From c862e8559d84bc975e35dc6618cb4c0888cef912 Mon Sep 17 00:00:00 2001 From: Mick de Graaf Date: Tue, 23 Apr 2024 09:42:20 +0200 Subject: [PATCH 001/316] forge install: forge-std v1.8.1 --- .gitmodules | 3 +++ lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 lib/forge-std diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..888d42dc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 00000000..bb4ceea9 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit bb4ceea94d6f10eeb5b41dc2391c6c8bf8e734ef From 7adcf12d614eb84d7ec9c19a762dd9d5e4861282 Mon Sep 17 00:00:00 2001 From: Mick de Graaf Date: Tue, 23 Apr 2024 11:23:57 +0200 Subject: [PATCH 002/316] Create FourSixTwoSixAgg.sol --- src/FourSixTwoSixAgg.sol | 232 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 src/FourSixTwoSixAgg.sol diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol new file mode 100644 index 00000000..89d31e36 --- /dev/null +++ b/src/FourSixTwoSixAgg.sol @@ -0,0 +1,232 @@ +/ SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Context} from "openzeppelin-contracts/utils/Context.sol"; +import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol"; +import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; +import {ERC4626} from "openzeppelin-contracts/token/ERC20/extensions/ERC4626.sol"; +import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; +import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; + +// @note Do NOT use with fee on transfer tokens +// @note Do NOT use with rebasing tokens +// @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol +contract FourSixTwoSixAgg is EVCUtil, ERC4626 { + uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; + uint8 internal constant REENTRANCYLOCK__LOCKED = 2; + + uint256 public constant INTEREST_SMEAR = 2 weeks; + + struct ESRSlot { + uint40 lastInterestUpdate; + uint40 interestSmearEnd; + uint168 interestLeft; + uint8 locked; + } + + struct Strategy { + uint128 allocated; + uint128 allocationPoints; + } + + mapping(address => Strategy) internal strategies; + address[] withdrawalQueue; + uint256 totalAllocated; + uint256 totalAllocationPoints; + + ESRSlot internal esrSlot; + + uint256 internal totalAssetsDeposited; + + error Reentrancy(); + error ArrayLengthMismatch(); + error AddressesOutOfOrder(); + error DuplicateInitialStrategy(); + error InitialAllocationPointsZero(); + + /// @notice Modifier to require an account status check on the EVC. + /// @dev Calls `requireAccountStatusCheck` function from EVC for the specified account after the function body. + /// @param account The address of the account to check. + modifier requireAccountStatusCheck(address account) { + _; + evc.requireAccountStatusCheck(account); + } + + modifier nonReentrant() { + if (esrSlot.locked == REENTRANCYLOCK__LOCKED) revert Reentrancy(); + + esrSlot.locked = REENTRANCYLOCK__LOCKED; + _; + esrSlot.locked = REENTRANCYLOCK__UNLOCKED; + } + + constructor(IEVC _evc, address _asset, string memory _name, string memory _symbol, address[] memory _initialStrategies, uint256[] memory _initialAllocationPoints) + EVCUtil(address(_evc)) + ERC4626(IERC20(_asset)) + ERC20(_name, _symbol) + { + esrSlot.locked = REENTRANCYLOCK__UNLOCKED; + + if(_initialStrategies.length != _initialAllocatoinPoints.length) revert ArrayLengthMismatch(); + + for (uint256 i = 1; i < _initialStrategies.length; i++) { + // Prevent duplicates + if(strategies[initialStrategies[i]].allocationPoints != 0) revert DuplicateInitialStrategy(); + + // Add it to the mapping + strategies[intialStrategies[i]] = Strategy({ + allocated: 0, + allocationPoints: uint128(_initialAllocationPoints[i]) + }); + + // Add to the total allocation points + totalAllocationPoints += _initialAllocationPoints[i]; + + // Add to the withdrawal queue + withdrawalQueue.push(_initialStrategies[i]); + } + } + + function totalAssets() public view override returns (uint256) { + return totalAssetsDeposited + interestAccrued(); + } + + /// @notice Transfers a certain amount of tokens to a recipient. + /// @param to The recipient of the transfer. + /// @param amount The amount shares to transfer. + /// @return A boolean indicating whether the transfer was successful. + function transfer(address to, uint256 amount) + public + override (ERC20, IERC20) + nonReentrant + requireAccountStatusCheck(_msgSender()) + returns (bool) + { + return super.transfer(to, amount); + } + + /// @notice Transfers a certain amount of tokens from a sender to a recipient. + /// @param from The sender of the transfer. + /// @param to The recipient of the transfer. + /// @param amount The amount of shares to transfer. + /// @return A boolean indicating whether the transfer was successful. + function transferFrom(address from, address to, uint256 amount) + public + override (ERC20, IERC20) + nonReentrant + requireAccountStatusCheck(from) + returns (bool) + { + return super.transferFrom(from, to, amount); + } + + function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256) { + return super.deposit(assets, receiver); + } + + function mint(uint256 assets, address receiver) public override nonReentrant returns (uint256) { + return super.mint(assets, receiver); + } + + function withdraw(uint256 assets, address receiver, address owner) + public + override + nonReentrant + requireAccountStatusCheck(owner) + returns (uint256 shares) + { + // Move interest to totalAssetsDeposited + updateInterestAndReturnESRSlotCache(); + return super.withdraw(assets, receiver, owner); + } + + function redeem(uint256 shares, address receiver, address owner) + public + override + nonReentrant + requireAccountStatusCheck(owner) + returns (uint256 assets) + { + // Move interest to totalAssetsDeposited + updateInterestAndReturnESRSlotCache(); + return super.redeem(shares, receiver, owner); + } + + function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { + totalAssetsDeposited += assets; + super._deposit(caller, receiver, assets, shares); + } + + function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) + internal + override + { + totalAssetsDeposited -= assets; + super._withdraw(caller, receiver, owner, assets, shares); + } + + function gulp() public nonReentrant { + ESRSlot memory esrSlotCache = updateInterestAndReturnESRSlotCache(); + + uint256 assetBalance = IERC20(asset()).balanceOf(address(this)); + uint256 toGulp = assetBalance - totalAssetsDeposited - esrSlotCache.interestLeft; + + uint256 maxGulp = type(uint168).max - esrSlotCache.interestLeft; + if (toGulp > maxGulp) toGulp = maxGulp; // cap interest, allowing the vault to function + + esrSlotCache.interestSmearEnd = uint40(block.timestamp + INTEREST_SMEAR); + esrSlotCache.interestLeft += uint168(toGulp); // toGulp <= maxGulp <= max uint168 + + // write esrSlotCache back to storage in a single SSTORE + esrSlot = esrSlotCache; + } + + function updateInterestAndReturnESRSlotCache() public returns (ESRSlot memory) { + ESRSlot memory esrSlotCache = esrSlot; + uint256 accruedInterest = interestAccruedFromCache(esrSlotCache); + + // it's safe to down-cast because the accrued interest is a fraction of interest left + esrSlotCache.interestLeft -= uint168(accruedInterest); + esrSlotCache.lastInterestUpdate = uint40(block.timestamp); + // write esrSlotCache back to storage in a single SSTORE + esrSlot = esrSlotCache; + // Move interest accrued to totalAssetsDeposited + totalAssetsDeposited += accruedInterest; + + return esrSlotCache; + } + + function interestAccrued() public view returns (uint256) { + return interestAccruedFromCache(esrSlot); + } + + function interestAccruedFromCache(ESRSlot memory esrSlotCache) internal view returns (uint256) { + // If distribution ended, full amount is accrued + if (block.timestamp > esrSlotCache.interestSmearEnd) { + return esrSlotCache.interestLeft; + } + + // If just updated return 0 + if (esrSlotCache.lastInterestUpdate == block.timestamp) { + return 0; + } + + // Else return what has accrued + uint256 totalDuration = esrSlotCache.interestSmearEnd - esrSlotCache.lastInterestUpdate; + uint256 timePassed = block.timestamp - esrSlotCache.lastInterestUpdate; + + return esrSlotCache.interestLeft * timePassed / totalDuration; + } + + function getESRSlot() public view returns (ESRSlot memory) { + return esrSlot; + } + + /// @notice Retrieves the message sender in the context of the EVC. + /// @dev This function returns the account on behalf of which the current operation is being performed, which is + /// either msg.sender or the account authenticated by the EVC. + /// @return The address of the message sender. + function _msgSender() internal view override (Context, EVCUtil) returns (address) { + return EVCUtil._msgSender(); + } +} \ No newline at end of file From ca6986836ce6df038c39c7d52e98b6c4039af1d7 Mon Sep 17 00:00:00 2001 From: Mick de Graaf Date: Tue, 23 Apr 2024 11:24:10 +0200 Subject: [PATCH 003/316] forge install: ethereum-vault-connector --- .gitmodules | 3 +++ lib/ethereum-vault-connector | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/ethereum-vault-connector diff --git a/.gitmodules b/.gitmodules index 888d42dc..2e3aabb7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/ethereum-vault-connector"] + path = lib/ethereum-vault-connector + url = https://github.com/euler-xyz/ethereum-vault-connector diff --git a/lib/ethereum-vault-connector b/lib/ethereum-vault-connector new file mode 160000 index 00000000..9bc0c17a --- /dev/null +++ b/lib/ethereum-vault-connector @@ -0,0 +1 @@ +Subproject commit 9bc0c17afd6bed51cd9126d9f3b8fc31614d29a9 From 1005b5a3a9b68a2a9e3affd761908712438efffb Mon Sep 17 00:00:00 2001 From: Mick de Graaf Date: Tue, 23 Apr 2024 15:53:53 +0200 Subject: [PATCH 004/316] badabing badaboom --- remappings.txt | 6 +++ src/FourSixTwoSixAgg.sol | 92 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 remappings.txt diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 00000000..287f2700 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,6 @@ +ds-test/=lib/ethereum-vault-connector/lib/forge-std/lib/ds-test/src/ +erc4626-tests/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/lib/erc4626-tests/ +ethereum-vault-connector/=lib/ethereum-vault-connector/src/ +forge-std/=lib/forge-std/src/ +openzeppelin-contracts/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ +openzeppelin/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 89d31e36..9c897943 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -1,10 +1,12 @@ -/ SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import {Context} from "openzeppelin-contracts/utils/Context.sol"; import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol"; import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; +import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; import {ERC4626} from "openzeppelin-contracts/token/ERC20/extensions/ERC4626.sol"; +import {IERC4626} from "openzeppelin-contracts/interfaces/IERC4626.sol"; import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; @@ -12,6 +14,8 @@ import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; // @note Do NOT use with rebasing tokens // @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol contract FourSixTwoSixAgg is EVCUtil, ERC4626 { + using SafeERC20 for IERC20; + uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; uint8 internal constant REENTRANCYLOCK__LOCKED = 2; @@ -60,21 +64,26 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { esrSlot.locked = REENTRANCYLOCK__UNLOCKED; } - constructor(IEVC _evc, address _asset, string memory _name, string memory _symbol, address[] memory _initialStrategies, uint256[] memory _initialAllocationPoints) + constructor(IEVC _evc, address _asset, string memory _name, string memory _symbol, address[] memory _initialStrategies, uint256[] memory _initialAllocationPoints, uint256 _initialCashAllocationPoints) EVCUtil(address(_evc)) ERC4626(IERC20(_asset)) ERC20(_name, _symbol) { esrSlot.locked = REENTRANCYLOCK__UNLOCKED; - if(_initialStrategies.length != _initialAllocatoinPoints.length) revert ArrayLengthMismatch(); + if(_initialStrategies.length != _initialAllocationPoints.length) revert ArrayLengthMismatch(); + + strategies[address(0)] = Strategy({ + allocated: 0, + allocationPoints: uint128(_initialCashAllocationPoints) + }); for (uint256 i = 1; i < _initialStrategies.length; i++) { // Prevent duplicates - if(strategies[initialStrategies[i]].allocationPoints != 0) revert DuplicateInitialStrategy(); + if(strategies[_initialStrategies[i]].allocationPoints != 0) revert DuplicateInitialStrategy(); // Add it to the mapping - strategies[intialStrategies[i]] = Strategy({ + strategies[_initialStrategies[i]] = Strategy({ allocated: 0, allocationPoints: uint128(_initialAllocationPoints[i]) }); @@ -91,6 +100,11 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { return totalAssetsDeposited + interestAccrued(); } + function totalAssetsAllocatable() public view returns (uint256) { + // Whatever balance of asset this vault holds + whatever is allocated to strategies + return IERC20(asset()).balanceOf(address(this)) + totalAllocated; + } + /// @notice Transfers a certain amount of tokens to a recipient. /// @param to The recipient of the transfer. /// @param amount The amount shares to transfer. @@ -162,6 +176,9 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { override { totalAssetsDeposited -= assets; + + // TODO move assets from strategies to this address if needed + super._withdraw(caller, receiver, owner, assets, shares); } @@ -196,6 +213,71 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { return esrSlotCache; } + function rebalance(address strategy) public nonReentrant() { + if(strategy == address(0)) { + return; //nothing to rebalance as this is the cash reserve + } + + // Harvest profits, also gulps and updates interest + harvest(strategy); + + Strategy memory strategyData = strategies[strategy]; + uint256 totalAllocationPointsCache = totalAllocationPoints; + uint256 totalAssetsAllocatableCache = totalAssetsAllocatable(); + uint256 targetAllocation = totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; + uint256 currentAllocation = strategyData.allocated; + + if (currentAllocation > targetAllocation) { + // Withdraw + uint256 toWithdraw = currentAllocation - targetAllocation; + IERC4626(strategy).withdraw(toWithdraw, address(this), address(this)); + strategies[strategy].allocated = uint128(targetAllocation); //TODO casting + // TOOD modify totalAllocated + // TODO potential reported losses + } + else if (currentAllocation < targetAllocation) { + // Deposit + uint256 targetCash = totalAssetsAllocatableCache * strategies[address(0)].allocationPoints / totalAllocationPointsCache; + uint256 currentCash = totalAssetsAllocatableCache - totalAllocated; + + // Calculate available cash to put in strategies + uint256 cashAvailable; + if (targetCash > currentCash) { + cashAvailable = targetCash - currentCash; + } else { + cashAvailable = 0; + } + + uint256 toDeposit = targetAllocation - currentAllocation; + if (toDeposit > cashAvailable) { + toDeposit = cashAvailable; + } + + // Do required approval (safely) and deposit + IERC20(asset()).safeApprove(strategy, toDeposit); + IERC4626(strategy).deposit(toDeposit, address(this)); + } + } + + function harvest(address strategy) public nonReentrant() { + Strategy memory strategyData = strategies[strategy]; + uint256 sharesBalance = IERC4626(strategy).balanceOf(address(this)); + uint256 underlyingBalance = IERC4626(strategy).convertToAssets(sharesBalance); + + // There's yield! + if(underlyingBalance > strategyData.allocated) { + uint256 yield = underlyingBalance - strategyData.allocated; + // Withdraw yield + IERC4626(strategy).withdraw(yield, address(this), address(this)); + } else { + // TODO handle losses + revert("For now we panic on negative yiedld"); + } + + // TODO gulp yield without withdraws + gulp(); + } + function interestAccrued() public view returns (uint256) { return interestAccruedFromCache(esrSlot); } From e6cb1a34eb29afb918cca0c42ee01e039e70705b Mon Sep 17 00:00:00 2001 From: Mick de Graaf Date: Tue, 23 Apr 2024 15:57:54 +0200 Subject: [PATCH 005/316] Gulp also surplus which is allocated --- src/FourSixTwoSixAgg.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 9c897943..8711a438 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -184,9 +184,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { function gulp() public nonReentrant { ESRSlot memory esrSlotCache = updateInterestAndReturnESRSlotCache(); - - uint256 assetBalance = IERC20(asset()).balanceOf(address(this)); - uint256 toGulp = assetBalance - totalAssetsDeposited - esrSlotCache.interestLeft; + uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft; uint256 maxGulp = type(uint168).max - esrSlotCache.interestLeft; if (toGulp > maxGulp) toGulp = maxGulp; // cap interest, allowing the vault to function From 53e911589b303ac11c04c5d2d5a3292058c7c969 Mon Sep 17 00:00:00 2001 From: Mick de Graaf Date: Tue, 23 Apr 2024 15:59:25 +0200 Subject: [PATCH 006/316] Do not withdraw yield but just account for it --- src/FourSixTwoSixAgg.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 8711a438..f4bef866 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -265,14 +265,13 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { // There's yield! if(underlyingBalance > strategyData.allocated) { uint256 yield = underlyingBalance - strategyData.allocated; - // Withdraw yield - IERC4626(strategy).withdraw(yield, address(this), address(this)); + strategies[strategy].allocated = uint128(underlyingBalance); + totalAllocated += yield; } else { // TODO handle losses - revert("For now we panic on negative yiedld"); + revert("For now we panic on negative yield"); } - // TODO gulp yield without withdraws gulp(); } From f01b19685b95b3a8b41ea11ac351f2b2c8961a34 Mon Sep 17 00:00:00 2001 From: Mick de Graaf Date: Tue, 23 Apr 2024 16:34:00 +0200 Subject: [PATCH 007/316] WIP withdraw from queue --- src/FourSixTwoSixAgg.sol | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index f4bef866..49c6e90d 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -177,7 +177,40 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { { totalAssetsDeposited -= assets; - // TODO move assets from strategies to this address if needed + uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); + for(uint256 i = 0; i < withdrawalQueue.length; i ++) { + if(assetsRetrieved >= assets) { + break; + } + + // TODO Should harvest from strategy here + harvest(strategy); + + Strategy memory strategyData = strategies[withdrawalQueue[i]]; + IERC4626 strategy = IERC4626(withdrawalQueue[i]); + uint256 sharesBalance = strategy.balanceOf(address(this)); + uint256 underlyingBalance = strategy.convertToAssets(sharesBalance); + + uint256 desiredAssets = assets - assetsRetrieved; + uint256 withdrawAmount; + // We can take all we need 🎉 + if(underlyingBalance > desiredAssets) { + withdrawAmount = desiredAssets; + } else { // not enough but take all we can + withdrawAmount = underlyingBalance; + } + + // Update allocated assets + strategies[withdrawalQueue[i]].allocated = strategyData.allocated - withdrawAmount; + totalAllocated -= withdrawAmount; + + // Do actual withdraw from strategy + strategy.withdraw(withdrawAmount, address(this), address(this)); + } + + if(assetsRetrieved < assets) { + revert("Not enough assets to withdraw"); + } super._withdraw(caller, receiver, owner, assets, shares); } @@ -257,6 +290,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { } } + // Todo allow batch harvest function harvest(address strategy) public nonReentrant() { Strategy memory strategyData = strategies[strategy]; uint256 sharesBalance = IERC4626(strategy).balanceOf(address(this)); From 4a95beda28235ce00f1a22b47092cfb7c8de5b4a Mon Sep 17 00:00:00 2001 From: Mick de Graaf Date: Tue, 23 Apr 2024 16:45:41 +0200 Subject: [PATCH 008/316] update allocated on rebalance --- src/FourSixTwoSixAgg.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 49c6e90d..13cc7dc7 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -260,14 +260,15 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { if (currentAllocation > targetAllocation) { // Withdraw + // TODO handle maxWithdraw uint256 toWithdraw = currentAllocation - targetAllocation; IERC4626(strategy).withdraw(toWithdraw, address(this), address(this)); strategies[strategy].allocated = uint128(targetAllocation); //TODO casting - // TOOD modify totalAllocated - // TODO potential reported losses + totalAllocated -= toWithdraw; } else if (currentAllocation < targetAllocation) { // Deposit + // TODO handle maxDeposit uint256 targetCash = totalAssetsAllocatableCache * strategies[address(0)].allocationPoints / totalAllocationPointsCache; uint256 currentCash = totalAssetsAllocatableCache - totalAllocated; @@ -287,6 +288,8 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { // Do required approval (safely) and deposit IERC20(asset()).safeApprove(strategy, toDeposit); IERC4626(strategy).deposit(toDeposit, address(this)); + strategies[strategy].allocated = uint128(currentAllocation + toDeposit); + totalAllocated += toDeposit; } } From 4512ff74ff216baf8d02b7cce65bf2e91987add6 Mon Sep 17 00:00:00 2001 From: Mick de Graaf Date: Wed, 24 Apr 2024 15:32:14 +0200 Subject: [PATCH 009/316] Aggregator management --- src/FourSixTwoSixAgg.sol | 149 +++++++++++++++++++++++++++++++-------- 1 file changed, 119 insertions(+), 30 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 13cc7dc7..f57771e7 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -9,16 +9,28 @@ import {ERC4626} from "openzeppelin-contracts/token/ERC20/extensions/ERC4626.sol import {IERC4626} from "openzeppelin-contracts/interfaces/IERC4626.sol"; import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; +import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol"; // @note Do NOT use with fee on transfer tokens // @note Do NOT use with rebasing tokens // @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol -contract FourSixTwoSixAgg is EVCUtil, ERC4626 { +// @note expired by Yearn v3 ❤️ +// TODO addons for reward stream support +contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { using SafeERC20 for IERC20; + // ROLES + bytes32 public constant ALLOCATION_ADJUSTER_ROLE = keccak256("ALLOCATION_ADJUSTER_ROLE"); + bytes32 public constant ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE = keccak256("ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE"); + bytes32 public constant WITHDRAW_QUEUE_REORDERER_ROLE = keccak256("WITHDRAW_QUEUE_REORDERER_ROLE"); + bytes32 public constant WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE = keccak256("WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE"); + bytes32 public constant STRATEGY_ADDER_ROLE = keccak256("STRATEGY_ADDER_ROLE"); + bytes32 public constant STRATEGY_ADDER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_ADDER_ROLE_ADMIN_ROLE"); + bytes32 public constant STRATEGY_REMOVER_ROLE = keccak256("STRATEGY_REMOVER_ROLE"); + bytes32 public constant STRATEGY_REMOVER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMIN_ROLE"); + uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; uint8 internal constant REENTRANCYLOCK__LOCKED = 2; - uint256 public constant INTEREST_SMEAR = 2 weeks; struct ESRSlot { @@ -29,8 +41,9 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { } struct Strategy { - uint128 allocated; - uint128 allocationPoints; + uint120 allocated; + uint120 allocationPoints; + bool active; } mapping(address => Strategy) internal strategies; @@ -64,7 +77,14 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { esrSlot.locked = REENTRANCYLOCK__UNLOCKED; } - constructor(IEVC _evc, address _asset, string memory _name, string memory _symbol, address[] memory _initialStrategies, uint256[] memory _initialAllocationPoints, uint256 _initialCashAllocationPoints) + constructor( + IEVC _evc, + address _asset, + string memory _name, + string memory _symbol, + uint256 _initialCashAllocationPoints, + + ) EVCUtil(address(_evc)) ERC4626(IERC20(_asset)) ERC20(_name, _symbol) @@ -73,27 +93,21 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { if(_initialStrategies.length != _initialAllocationPoints.length) revert ArrayLengthMismatch(); + if(_initialCashAllocationPoints == 0) revert InitialAllocationPointsZero(); strategies[address(0)] = Strategy({ allocated: 0, - allocationPoints: uint128(_initialCashAllocationPoints) + allocationPoints: uint120(_initialCashAllocationPoints), + active: true }); - for (uint256 i = 1; i < _initialStrategies.length; i++) { - // Prevent duplicates - if(strategies[_initialStrategies[i]].allocationPoints != 0) revert DuplicateInitialStrategy(); - - // Add it to the mapping - strategies[_initialStrategies[i]] = Strategy({ - allocated: 0, - allocationPoints: uint128(_initialAllocationPoints[i]) - }); - - // Add to the total allocation points - totalAllocationPoints += _initialAllocationPoints[i]; + // Setup DEFAULT_ADMIN + _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); - // Add to the withdrawal queue - withdrawalQueue.push(_initialStrategies[i]); - } + // Setup role admins + _setRoleAdmin(ALLOCATION_ADJUSTER_ROLE, ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE); + _setRoleAdmin(WITHDRAW_QUEUE_REORDERER_ROLE, WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE); + _setRoleAdmin(STRATEGY_ADDER_ROLE, STRATEGY_ADDER_ROLE_ADMIN_ROLE); + _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMIN_ROLE); } function totalAssets() public view override returns (uint256) { @@ -183,11 +197,9 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { break; } - // TODO Should harvest from strategy here - harvest(strategy); - Strategy memory strategyData = strategies[withdrawalQueue[i]]; IERC4626 strategy = IERC4626(withdrawalQueue[i]); + harvest(address(strategy)); uint256 sharesBalance = strategy.balanceOf(address(this)); uint256 underlyingBalance = strategy.convertToAssets(sharesBalance); @@ -201,7 +213,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { } // Update allocated assets - strategies[withdrawalQueue[i]].allocated = strategyData.allocated - withdrawAmount; + strategies[withdrawalQueue[i]].allocated = strategyData.allocated - uint120(withdrawAmount); totalAllocated -= withdrawAmount; // Do actual withdraw from strategy @@ -262,13 +274,18 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { // Withdraw // TODO handle maxWithdraw uint256 toWithdraw = currentAllocation - targetAllocation; + + uint256 maxWithdraw = IERC4626(strategy).maxWithdraw(address(this)); + if(toWithdraw > maxWithdraw) { + toWithdraw = maxWithdraw; + } + IERC4626(strategy).withdraw(toWithdraw, address(this), address(this)); - strategies[strategy].allocated = uint128(targetAllocation); //TODO casting + strategies[strategy].allocated = uint120(targetAllocation); //TODO casting totalAllocated -= toWithdraw; } else if (currentAllocation < targetAllocation) { // Deposit - // TODO handle maxDeposit uint256 targetCash = totalAssetsAllocatableCache * strategies[address(0)].allocationPoints / totalAllocationPointsCache; uint256 currentCash = totalAssetsAllocatableCache - totalAllocated; @@ -285,15 +302,24 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { toDeposit = cashAvailable; } + uint256 maxDeposit = IERC4626(strategy).maxDeposit(address(this)); + if(toDeposit > maxDeposit) { + toDeposit = maxDeposit; + } + + if(toDeposit == 0) { + return; // No cash to deposit + } + // Do required approval (safely) and deposit IERC20(asset()).safeApprove(strategy, toDeposit); IERC4626(strategy).deposit(toDeposit, address(this)); - strategies[strategy].allocated = uint128(currentAllocation + toDeposit); + strategies[strategy].allocated = uint120(currentAllocation + toDeposit); totalAllocated += toDeposit; } } - // Todo allow batch harvest + // Todo possibly allow batch harvest function harvest(address strategy) public nonReentrant() { Strategy memory strategyData = strategies[strategy]; uint256 sharesBalance = IERC4626(strategy).balanceOf(address(this)); @@ -302,8 +328,9 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { // There's yield! if(underlyingBalance > strategyData.allocated) { uint256 yield = underlyingBalance - strategyData.allocated; - strategies[strategy].allocated = uint128(underlyingBalance); + strategies[strategy].allocated = uint120(underlyingBalance); totalAllocated += yield; + // TODO possible performance fee } else { // TODO handle losses revert("For now we panic on negative yield"); @@ -312,6 +339,68 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626 { gulp(); } + function adjustAllocationPoints(address strategy, uint256 newPoints) public nonReentrant() onlyRole(ALLOCATION_ADJUSTER_ROLE) { + Strategy memory strategyData = strategies[strategy]; + uint256 totalAllocationPointsCache = totalAllocationPoints; + + if(strategyData.active = false) { + revert("Strategy is inactive"); + } + + strategies[strategy].allocationPoints = uint120(newPoints); + if(newPoints > strategyData.allocationPoints) { + uint256 diff = newPoints - strategyData.allocationPoints; + totalAllocationPoints + totalAllocationPointsCache + diff; + } else if(newPoints < strategyData.allocationPoints) { + uint256 diff = strategyData.allocationPoints - newPoints; + totalAllocationPoints = totalAllocationPointsCache - diff; + } + } + + function reorderWithdrawalQueue(uint8 index1, uint8 index2) public nonReentrant() onlyRole(WITHDRAW_QUEUE_REORDERER_ROLE) { + if(index1 >= withdrawalQueue.length || index2 >= withdrawalQueue.length) { + revert("Index out of bounds"); + } + + if(index1 == index2) { + revert("Indexes are the same"); + } + + address temp = withdrawalQueue[index1]; + withdrawalQueue[index1] = withdrawalQueue[index2]; + withdrawalQueue[index2] = temp; + } + + function addStrategy(address strategy, uint256 allocationPoints) public nonReentrant() onlyRole(STRATEGY_ADDER_ROLE) { + if(IERC4626(strategy).asset() != asset()) { + revert ("Strategy asset does not match vault asset"); + } + + if(strategies[strategy].active) { + revert("Strategy already exists"); + } + + strategies[strategy] = Strategy({ + allocated: 0, + allocationPoints: uint120(allocationPoints), + active: true + }); + + totalAllocationPoints += allocationPoints; + withdrawalQueue.push(strategy); + } + + // remove strategy, sets its allocation points to zero. Does not pull funds, `harvest` needs to be called to withdraw + function removeStrategy(address strategy) public nonReentrant() onlyRole(STRATEGY_REMOVER_ROLE) { + if(!strategies[strategy].active) { + revert("Strategy is already inactive"); + } + + strategies[strategy].active = false; + totalAllocationPoints -= strategies[strategy].allocationPoints; + strategies[strategy].allocationPoints = 0; + } + function interestAccrued() public view returns (uint256) { return interestAccruedFromCache(esrSlot); } From 35cd6db2433b6379f8021d58fee004d08f0d81ee Mon Sep 17 00:00:00 2001 From: Mick de Graaf Date: Thu, 25 Apr 2024 10:24:26 +0200 Subject: [PATCH 010/316] Notes --- src/FourSixTwoSixAgg.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index f57771e7..b82bbbec 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -16,6 +16,7 @@ import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessContr // @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol // @note expired by Yearn v3 ❤️ // TODO addons for reward stream support +// TODO custom withdraw queue support contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { using SafeERC20 for IERC20; @@ -399,6 +400,8 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { strategies[strategy].active = false; totalAllocationPoints -= strategies[strategy].allocationPoints; strategies[strategy].allocationPoints = 0; + + // TODO remove from withdrawalQueue } function interestAccrued() public view returns (uint256) { From 1ff1cedfe07470e2dbfdb8e59d7ffe3dde95dacd Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 2 May 2024 12:31:15 +0100 Subject: [PATCH 011/316] fix: build --- src/FourSixTwoSixAgg.sol | 50 ++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index b82bbbec..ede8caf7 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -20,6 +20,15 @@ import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessContr contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { using SafeERC20 for IERC20; + error Reentrancy(); + error ArrayLengthMismatch(); + error AddressesOutOfOrder(); + error DuplicateInitialStrategy(); + error InitialAllocationPointsZero(); + + uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; + uint8 internal constant REENTRANCYLOCK__LOCKED = 2; + // ROLES bytes32 public constant ALLOCATION_ADJUSTER_ROLE = keccak256("ALLOCATION_ADJUSTER_ROLE"); bytes32 public constant ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE = keccak256("ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE"); @@ -30,10 +39,17 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { bytes32 public constant STRATEGY_REMOVER_ROLE = keccak256("STRATEGY_REMOVER_ROLE"); bytes32 public constant STRATEGY_REMOVER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMIN_ROLE"); - uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; - uint8 internal constant REENTRANCYLOCK__LOCKED = 2; uint256 public constant INTEREST_SMEAR = 2 weeks; + ESRSlot internal esrSlot; + uint256 internal totalAssetsDeposited; + + uint256 totalAllocated; + uint256 totalAllocationPoints; + + mapping(address => Strategy) internal strategies; + address[] withdrawalQueue; + struct ESRSlot { uint40 lastInterestUpdate; uint40 interestSmearEnd; @@ -47,21 +63,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { bool active; } - mapping(address => Strategy) internal strategies; - address[] withdrawalQueue; - uint256 totalAllocated; - uint256 totalAllocationPoints; - - ESRSlot internal esrSlot; - - uint256 internal totalAssetsDeposited; - - error Reentrancy(); - error ArrayLengthMismatch(); - error AddressesOutOfOrder(); - error DuplicateInitialStrategy(); - error InitialAllocationPointsZero(); - /// @notice Modifier to require an account status check on the EVC. /// @dev Calls `requireAccountStatusCheck` function from EVC for the specified account after the function body. /// @param account The address of the account to check. @@ -84,7 +85,8 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { string memory _name, string memory _symbol, uint256 _initialCashAllocationPoints, - + address[] memory _initialStrategies, + uint256[] memory _initialStrategiesAllocationPoints ) EVCUtil(address(_evc)) ERC4626(IERC20(_asset)) @@ -92,15 +94,23 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { { esrSlot.locked = REENTRANCYLOCK__UNLOCKED; - if(_initialStrategies.length != _initialAllocationPoints.length) revert ArrayLengthMismatch(); - + if(_initialStrategies.length != _initialStrategiesAllocationPoints.length) revert ArrayLengthMismatch(); if(_initialCashAllocationPoints == 0) revert InitialAllocationPointsZero(); + strategies[address(0)] = Strategy({ allocated: 0, allocationPoints: uint120(_initialCashAllocationPoints), active: true }); + for(uint256 i; i < _initialStrategies.length; ++i) { + strategies[_initialStrategies[i]] = Strategy({ + allocated: 0, + allocationPoints: uint120(_initialStrategiesAllocationPoints[i]), + active: true + }); + } + // Setup DEFAULT_ADMIN _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); From 1287ee622005039aad736b83832782d13894b398 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 2 May 2024 13:56:29 +0100 Subject: [PATCH 012/316] chore: clean --- foundry.toml | 59 ++++++++++++++++++++++++++++ src/Counter.sol | 14 ------- test/Counter.t.sol | 24 ----------- test/unit/FourSixTwoSixAggBase.t.sol | 26 ++++++++++++ 4 files changed, 85 insertions(+), 38 deletions(-) delete mode 100644 src/Counter.sol delete mode 100644 test/Counter.t.sol create mode 100644 test/unit/FourSixTwoSixAggBase.t.sol diff --git a/foundry.toml b/foundry.toml index 25b918f9..358eeffd 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,5 +2,64 @@ src = "src" out = "out" libs = ["lib"] +test = 'test' +optimizer = true +optimizer_runs = 20_000 +solc = "0.8.23" +gas_reports = ["*"] +fs_permissions = [{ access = "read", path = "./"}] + +[profile.default.fmt] +line_length = 120 +tab_width = 4 +bracket_spacing = false +int_types = "long" +quote_style = "double" +number_underscore = "preserve" +override_spacing = true + +[profile.fuzz] +runs = 1000 +max_local_rejects = 1024 +max_global_rejects = 65536 +seed = '0x3e8' +dictionary_weight = 100 +include_storage = true +include_push_bytes = true +match_test = "Fuzzing" +match_contract = "Fuzzing" + +[profile.ci_fuzz] +runs = 50000 +max_local_rejects = 1024 +max_global_rejects = 65536 +seed = '0x3e8' +dictionary_weight = 100 +include_storage = true +include_push_bytes = true +match_test = "Fuzzing" +match_contract = "Fuzzing" + +[profile.invariant] +runs = 256 +depth = 15 +fail_on_revert = false +call_override = false +dictionary_weight = 80 +include_storage = true +include_push_bytes = true + +[profile.coverage] +via_ir = true +no_match_test = "Fuzzing" +no_match_contract = "Script" + +[profile.coverage.optimizer_details] +constantOptimizer = true +yul = true + +[profile.coverage.optimizer_details.yulDetails] +stackAllocation = true +optimizerSteps = '' # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded7997..00000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 54b724f7..00000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/test/unit/FourSixTwoSixAggBase.t.sol b/test/unit/FourSixTwoSixAggBase.t.sol new file mode 100644 index 00000000..6c4c0190 --- /dev/null +++ b/test/unit/FourSixTwoSixAggBase.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {EVaultTestBase} from "../../evault/EVaultTestBase.t.sol"; +import {IEVault, IERC20} from "src/EVault/IEVault.sol"; +import {IRMTestDefault} from "../../../mocks/IRMTestDefault.sol"; +import {ESynth} from "src/Synths/ESynth.sol"; +import {TestERC20} from "../../../mocks/TestERC20.sol"; + +contract ESynthTest is EVaultTestBase { + ESynth esynth; + address user1; + address user2; + + function setUp() public virtual override { + super.setUp(); + + user1 = vm.addr(1001); + user2 = vm.addr(1002); + + esynth = ESynth(address(new ESynth(evc, "Test Synth", "TST"))); + assetTST = TestERC20(address(esynth)); + + eTST = createSynthEVault(address(assetTST)); + } +} \ No newline at end of file From ebe99eacdee5803d37c7303b290ec8aae6724071 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 2 May 2024 13:59:09 +0100 Subject: [PATCH 013/316] forge install: euler-vault-kit e75770023e1b432a660828120cc166b7dc64a222 --- .gitmodules | 3 +++ lib/euler-vault-kit | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/euler-vault-kit diff --git a/.gitmodules b/.gitmodules index 2e3aabb7..0e9f83a5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/ethereum-vault-connector"] path = lib/ethereum-vault-connector url = https://github.com/euler-xyz/ethereum-vault-connector +[submodule "lib/euler-vault-kit"] + path = lib/euler-vault-kit + url = https://github.com/euler-xyz/euler-vault-kit diff --git a/lib/euler-vault-kit b/lib/euler-vault-kit new file mode 160000 index 00000000..e7577002 --- /dev/null +++ b/lib/euler-vault-kit @@ -0,0 +1 @@ +Subproject commit e75770023e1b432a660828120cc166b7dc64a222 From b32c90eee529ae142fc476502f0b2fbc2e0c5f4a Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 2 May 2024 16:37:41 +0100 Subject: [PATCH 014/316] init FourSixTwoSixAggBase for unit tests --- remappings.txt | 1 + test/unit/FourSixTwoSixAggBase.t.sol | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/remappings.txt b/remappings.txt index 287f2700..f65498dc 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,3 +4,4 @@ ethereum-vault-connector/=lib/ethereum-vault-connector/src/ forge-std/=lib/forge-std/src/ openzeppelin-contracts/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ openzeppelin/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ +evk/=lib/euler-vault-kit/ diff --git a/test/unit/FourSixTwoSixAggBase.t.sol b/test/unit/FourSixTwoSixAggBase.t.sol index 6c4c0190..a7cc1d41 100644 --- a/test/unit/FourSixTwoSixAggBase.t.sol +++ b/test/unit/FourSixTwoSixAggBase.t.sol @@ -1,26 +1,24 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {EVaultTestBase} from "../../evault/EVaultTestBase.t.sol"; -import {IEVault, IERC20} from "src/EVault/IEVault.sol"; -import {IRMTestDefault} from "../../../mocks/IRMTestDefault.sol"; -import {ESynth} from "src/Synths/ESynth.sol"; -import {TestERC20} from "../../../mocks/TestERC20.sol"; +import {EVaultTestBase, TestERC20} from "evk/test/unit/evault/EVaultTestBase.t.sol"; +import {FourSixTwoSixAgg} from "../../src/FourSixTwoSixAgg.sol"; -contract ESynthTest is EVaultTestBase { - ESynth esynth; +contract FourSixTwoSixAggBase is EVaultTestBase { + address deployer; address user1; address user2; + FourSixTwoSixAgg fourSixTwoSixAgg; + function setUp() public virtual override { super.setUp(); + + deployer = makeAddr("Deployer"); + user1 = makeAddr("User_1"); + user2 = makeAddr("User_2"); - user1 = vm.addr(1001); - user2 = vm.addr(1002); - - esynth = ESynth(address(new ESynth(evc, "Test Synth", "TST"))); - assetTST = TestERC20(address(esynth)); - - eTST = createSynthEVault(address(assetTST)); + vm.prank(deployer); + fourSixTwoSixAgg = new FourSixTwoSixAgg(evc, address(assetTST), "assetTST_Agg", "assetTST_Agg", type(uint120).max, new address[](0), new uint256[](0)); } } \ No newline at end of file From 0b51679440e685d8579a11e0bdd11ba056e039c3 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 2 May 2024 16:37:55 +0100 Subject: [PATCH 015/316] chore fmt --- src/FourSixTwoSixAgg.sol | 119 ++++++++++++++------------- test/unit/FourSixTwoSixAggBase.t.sol | 14 +++- 2 files changed, 71 insertions(+), 62 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index ede8caf7..dbe8ea2c 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -33,7 +33,8 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { bytes32 public constant ALLOCATION_ADJUSTER_ROLE = keccak256("ALLOCATION_ADJUSTER_ROLE"); bytes32 public constant ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE = keccak256("ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE"); bytes32 public constant WITHDRAW_QUEUE_REORDERER_ROLE = keccak256("WITHDRAW_QUEUE_REORDERER_ROLE"); - bytes32 public constant WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE = keccak256("WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE"); + bytes32 public constant WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE = + keccak256("WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE"); bytes32 public constant STRATEGY_ADDER_ROLE = keccak256("STRATEGY_ADDER_ROLE"); bytes32 public constant STRATEGY_ADDER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_ADDER_ROLE_ADMIN_ROLE"); bytes32 public constant STRATEGY_REMOVER_ROLE = keccak256("STRATEGY_REMOVER_ROLE"); @@ -87,28 +88,18 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { uint256 _initialCashAllocationPoints, address[] memory _initialStrategies, uint256[] memory _initialStrategiesAllocationPoints - ) - EVCUtil(address(_evc)) - ERC4626(IERC20(_asset)) - ERC20(_name, _symbol) - { + ) EVCUtil(address(_evc)) ERC4626(IERC20(_asset)) ERC20(_name, _symbol) { esrSlot.locked = REENTRANCYLOCK__UNLOCKED; - if(_initialStrategies.length != _initialStrategiesAllocationPoints.length) revert ArrayLengthMismatch(); - if(_initialCashAllocationPoints == 0) revert InitialAllocationPointsZero(); - - strategies[address(0)] = Strategy({ - allocated: 0, - allocationPoints: uint120(_initialCashAllocationPoints), - active: true - }); - - for(uint256 i; i < _initialStrategies.length; ++i) { - strategies[_initialStrategies[i]] = Strategy({ - allocated: 0, - allocationPoints: uint120(_initialStrategiesAllocationPoints[i]), - active: true - }); + if (_initialStrategies.length != _initialStrategiesAllocationPoints.length) revert ArrayLengthMismatch(); + if (_initialCashAllocationPoints == 0) revert InitialAllocationPointsZero(); + + strategies[address(0)] = + Strategy({allocated: 0, allocationPoints: uint120(_initialCashAllocationPoints), active: true}); + + for (uint256 i; i < _initialStrategies.length; ++i) { + strategies[_initialStrategies[i]] = + Strategy({allocated: 0, allocationPoints: uint120(_initialStrategiesAllocationPoints[i]), active: true}); } // Setup DEFAULT_ADMIN @@ -203,8 +194,8 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); - for(uint256 i = 0; i < withdrawalQueue.length; i ++) { - if(assetsRetrieved >= assets) { + for (uint256 i = 0; i < withdrawalQueue.length; i++) { + if (assetsRetrieved >= assets) { break; } @@ -217,9 +208,10 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { uint256 desiredAssets = assets - assetsRetrieved; uint256 withdrawAmount; // We can take all we need 🎉 - if(underlyingBalance > desiredAssets) { + if (underlyingBalance > desiredAssets) { withdrawAmount = desiredAssets; - } else { // not enough but take all we can + } else { + // not enough but take all we can withdrawAmount = underlyingBalance; } @@ -231,7 +223,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { strategy.withdraw(withdrawAmount, address(this), address(this)); } - if(assetsRetrieved < assets) { + if (assetsRetrieved < assets) { revert("Not enough assets to withdraw"); } @@ -267,18 +259,19 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return esrSlotCache; } - function rebalance(address strategy) public nonReentrant() { - if(strategy == address(0)) { + function rebalance(address strategy) public nonReentrant { + if (strategy == address(0)) { return; //nothing to rebalance as this is the cash reserve } // Harvest profits, also gulps and updates interest - harvest(strategy); + harvest(strategy); Strategy memory strategyData = strategies[strategy]; uint256 totalAllocationPointsCache = totalAllocationPoints; uint256 totalAssetsAllocatableCache = totalAssetsAllocatable(); - uint256 targetAllocation = totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; + uint256 targetAllocation = + totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; uint256 currentAllocation = strategyData.allocated; if (currentAllocation > targetAllocation) { @@ -287,17 +280,17 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { uint256 toWithdraw = currentAllocation - targetAllocation; uint256 maxWithdraw = IERC4626(strategy).maxWithdraw(address(this)); - if(toWithdraw > maxWithdraw) { + if (toWithdraw > maxWithdraw) { toWithdraw = maxWithdraw; } IERC4626(strategy).withdraw(toWithdraw, address(this), address(this)); strategies[strategy].allocated = uint120(targetAllocation); //TODO casting totalAllocated -= toWithdraw; - } - else if (currentAllocation < targetAllocation) { + } else if (currentAllocation < targetAllocation) { // Deposit - uint256 targetCash = totalAssetsAllocatableCache * strategies[address(0)].allocationPoints / totalAllocationPointsCache; + uint256 targetCash = + totalAssetsAllocatableCache * strategies[address(0)].allocationPoints / totalAllocationPointsCache; uint256 currentCash = totalAssetsAllocatableCache - totalAllocated; // Calculate available cash to put in strategies @@ -314,11 +307,11 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } uint256 maxDeposit = IERC4626(strategy).maxDeposit(address(this)); - if(toDeposit > maxDeposit) { + if (toDeposit > maxDeposit) { toDeposit = maxDeposit; } - if(toDeposit == 0) { + if (toDeposit == 0) { return; // No cash to deposit } @@ -331,13 +324,13 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } // Todo possibly allow batch harvest - function harvest(address strategy) public nonReentrant() { + function harvest(address strategy) public nonReentrant { Strategy memory strategyData = strategies[strategy]; uint256 sharesBalance = IERC4626(strategy).balanceOf(address(this)); uint256 underlyingBalance = IERC4626(strategy).convertToAssets(sharesBalance); - + // There's yield! - if(underlyingBalance > strategyData.allocated) { + if (underlyingBalance > strategyData.allocated) { uint256 yield = underlyingBalance - strategyData.allocated; strategies[strategy].allocated = uint120(underlyingBalance); totalAllocated += yield; @@ -350,30 +343,38 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { gulp(); } - function adjustAllocationPoints(address strategy, uint256 newPoints) public nonReentrant() onlyRole(ALLOCATION_ADJUSTER_ROLE) { + function adjustAllocationPoints(address strategy, uint256 newPoints) + public + nonReentrant + onlyRole(ALLOCATION_ADJUSTER_ROLE) + { Strategy memory strategyData = strategies[strategy]; uint256 totalAllocationPointsCache = totalAllocationPoints; - if(strategyData.active = false) { + if (strategyData.active = false) { revert("Strategy is inactive"); } strategies[strategy].allocationPoints = uint120(newPoints); - if(newPoints > strategyData.allocationPoints) { + if (newPoints > strategyData.allocationPoints) { uint256 diff = newPoints - strategyData.allocationPoints; totalAllocationPoints + totalAllocationPointsCache + diff; - } else if(newPoints < strategyData.allocationPoints) { + } else if (newPoints < strategyData.allocationPoints) { uint256 diff = strategyData.allocationPoints - newPoints; totalAllocationPoints = totalAllocationPointsCache - diff; } } - function reorderWithdrawalQueue(uint8 index1, uint8 index2) public nonReentrant() onlyRole(WITHDRAW_QUEUE_REORDERER_ROLE) { - if(index1 >= withdrawalQueue.length || index2 >= withdrawalQueue.length) { + function reorderWithdrawalQueue(uint8 index1, uint8 index2) + public + nonReentrant + onlyRole(WITHDRAW_QUEUE_REORDERER_ROLE) + { + if (index1 >= withdrawalQueue.length || index2 >= withdrawalQueue.length) { revert("Index out of bounds"); } - if(index1 == index2) { + if (index1 == index2) { revert("Indexes are the same"); } @@ -382,28 +383,28 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { withdrawalQueue[index2] = temp; } - function addStrategy(address strategy, uint256 allocationPoints) public nonReentrant() onlyRole(STRATEGY_ADDER_ROLE) { - if(IERC4626(strategy).asset() != asset()) { - revert ("Strategy asset does not match vault asset"); + function addStrategy(address strategy, uint256 allocationPoints) + public + nonReentrant + onlyRole(STRATEGY_ADDER_ROLE) + { + if (IERC4626(strategy).asset() != asset()) { + revert("Strategy asset does not match vault asset"); } - if(strategies[strategy].active) { + if (strategies[strategy].active) { revert("Strategy already exists"); } - strategies[strategy] = Strategy({ - allocated: 0, - allocationPoints: uint120(allocationPoints), - active: true - }); + strategies[strategy] = Strategy({allocated: 0, allocationPoints: uint120(allocationPoints), active: true}); totalAllocationPoints += allocationPoints; withdrawalQueue.push(strategy); } - + // remove strategy, sets its allocation points to zero. Does not pull funds, `harvest` needs to be called to withdraw - function removeStrategy(address strategy) public nonReentrant() onlyRole(STRATEGY_REMOVER_ROLE) { - if(!strategies[strategy].active) { + function removeStrategy(address strategy) public nonReentrant onlyRole(STRATEGY_REMOVER_ROLE) { + if (!strategies[strategy].active) { revert("Strategy is already inactive"); } @@ -447,4 +448,4 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { function _msgSender() internal view override (Context, EVCUtil) returns (address) { return EVCUtil._msgSender(); } -} \ No newline at end of file +} diff --git a/test/unit/FourSixTwoSixAggBase.t.sol b/test/unit/FourSixTwoSixAggBase.t.sol index a7cc1d41..2a102513 100644 --- a/test/unit/FourSixTwoSixAggBase.t.sol +++ b/test/unit/FourSixTwoSixAggBase.t.sol @@ -13,12 +13,20 @@ contract FourSixTwoSixAggBase is EVaultTestBase { function setUp() public virtual override { super.setUp(); - + deployer = makeAddr("Deployer"); user1 = makeAddr("User_1"); user2 = makeAddr("User_2"); vm.prank(deployer); - fourSixTwoSixAgg = new FourSixTwoSixAgg(evc, address(assetTST), "assetTST_Agg", "assetTST_Agg", type(uint120).max, new address[](0), new uint256[](0)); + fourSixTwoSixAgg = new FourSixTwoSixAgg( + evc, + address(assetTST), + "assetTST_Agg", + "assetTST_Agg", + type(uint120).max, + new address[](0), + new uint256[](0) + ); } -} \ No newline at end of file +} From d25a69499be53f91d3e7026bb978a56655d3a0ef Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 2 May 2024 16:41:34 +0100 Subject: [PATCH 016/316] update github actions --- .github/workflows/test.yml | 116 +++++++++++++++++++++++++++---------- 1 file changed, 85 insertions(+), 31 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9282e829..1f06d088 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,34 +1,88 @@ -name: test +name: ci -on: workflow_dispatch - -env: - FOUNDRY_PROFILE: ci +on: + push: + branches: + - main + pull_request: jobs: - check: - strategy: - fail-fast: true - - name: Foundry project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Run Forge build - run: | - forge --version - forge build --sizes - id: build - - - name: Run Forge tests - run: | - forge test -vvv - id: test + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run foundry build + run: | + forge --version + forge build + id: build + + lint-check: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run foundry fmt check + run: | + forge fmt --check + id: fmt + + test: + runs-on: ubuntu-latest + needs: lint-check + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + - name: Run foundry tests + # --ast tests enables inline configs to work https://github.com/foundry-rs/foundry/issues/7310#issuecomment-1978088200 + run: | + forge test -vv --gas-report --ast + id: test + + fuzz: + runs-on: ubuntu-latest + needs: lint-check + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + - name: Run foundry tests + run: | + FOUNDRY_PROFILE=fuzz forge test -vv + id: fuzz + + coverage: + runs-on: ubuntu-latest + needs: lint-check + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + - name: Run foundry coverage + run: | + FOUNDRY_PROFILE=coverage forge coverage --report summary + id: coverage \ No newline at end of file From 4b0f8e8abfffa5ccfef2d2529ad2f9dcbf327897 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 2 May 2024 18:07:35 +0100 Subject: [PATCH 017/316] replace revert messages with custom errors --- src/FourSixTwoSixAgg.sol | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index dbe8ea2c..1307e188 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -25,6 +25,14 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { error AddressesOutOfOrder(); error DuplicateInitialStrategy(); error InitialAllocationPointsZero(); + error NotEnoughAssets(); + error NegativeYield(); + error InactiveStrategy(); + error OutOfBounds(); + error SameIndexes(); + error InvalidStrategyAsset(); + error StrategyAlreadyExist(); + error AlreadyRemoved(); uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; uint8 internal constant REENTRANCYLOCK__LOCKED = 2; @@ -224,7 +232,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } if (assetsRetrieved < assets) { - revert("Not enough assets to withdraw"); + revert NotEnoughAssets(); } super._withdraw(caller, receiver, owner, assets, shares); @@ -337,7 +345,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { // TODO possible performance fee } else { // TODO handle losses - revert("For now we panic on negative yield"); + revert NegativeYield(); } gulp(); @@ -352,7 +360,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { uint256 totalAllocationPointsCache = totalAllocationPoints; if (strategyData.active = false) { - revert("Strategy is inactive"); + revert InactiveStrategy(); } strategies[strategy].allocationPoints = uint120(newPoints); @@ -371,11 +379,11 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { onlyRole(WITHDRAW_QUEUE_REORDERER_ROLE) { if (index1 >= withdrawalQueue.length || index2 >= withdrawalQueue.length) { - revert("Index out of bounds"); + revert OutOfBounds(); } if (index1 == index2) { - revert("Indexes are the same"); + revert SameIndexes(); } address temp = withdrawalQueue[index1]; @@ -389,11 +397,11 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { onlyRole(STRATEGY_ADDER_ROLE) { if (IERC4626(strategy).asset() != asset()) { - revert("Strategy asset does not match vault asset"); + revert InvalidStrategyAsset(); } if (strategies[strategy].active) { - revert("Strategy already exists"); + revert StrategyAlreadyExist(); } strategies[strategy] = Strategy({allocated: 0, allocationPoints: uint120(allocationPoints), active: true}); @@ -405,7 +413,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { // remove strategy, sets its allocation points to zero. Does not pull funds, `harvest` needs to be called to withdraw function removeStrategy(address strategy) public nonReentrant onlyRole(STRATEGY_REMOVER_ROLE) { if (!strategies[strategy].active) { - revert("Strategy is already inactive"); + revert AlreadyRemoved(); } strategies[strategy].active = false; From de6987076a94a29d74fc77e7da397475f33544fa Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 2 May 2024 18:12:15 +0100 Subject: [PATCH 018/316] update config --- .github/workflows/test.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1f06d088..e621bc17 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -69,20 +69,20 @@ jobs: version: nightly - name: Run foundry tests run: | - FOUNDRY_PROFILE=fuzz forge test -vv + FOUNDRY_PROFILE=ci_fuzz forge test -vv id: fuzz - coverage: - runs-on: ubuntu-latest - needs: lint-check - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - name: Run foundry coverage - run: | - FOUNDRY_PROFILE=coverage forge coverage --report summary - id: coverage \ No newline at end of file + # coverage: + # runs-on: ubuntu-latest + # needs: lint-check + # steps: + # - uses: actions/checkout@v3 + # with: + # submodules: recursive + # - uses: foundry-rs/foundry-toolchain@v1 + # with: + # version: nightly + # - name: Run foundry coverage + # run: | + # FOUNDRY_PROFILE=coverage forge coverage --report summary + # id: coverage \ No newline at end of file From 4d8599c6f355e78814d2d0aec15e1e137b61c523 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 3 May 2024 12:51:17 +0100 Subject: [PATCH 019/316] fix: increase assetsRetrieved in withdraw() --- src/FourSixTwoSixAgg.sol | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 1307e188..69768c31 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -209,24 +209,22 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { Strategy memory strategyData = strategies[withdrawalQueue[i]]; IERC4626 strategy = IERC4626(withdrawalQueue[i]); + harvest(address(strategy)); + uint256 sharesBalance = strategy.balanceOf(address(this)); uint256 underlyingBalance = strategy.convertToAssets(sharesBalance); uint256 desiredAssets = assets - assetsRetrieved; - uint256 withdrawAmount; - // We can take all we need 🎉 - if (underlyingBalance > desiredAssets) { - withdrawAmount = desiredAssets; - } else { - // not enough but take all we can - withdrawAmount = underlyingBalance; - } + uint256 withdrawAmount = (underlyingBalance >= desiredAssets) ? desiredAssets : underlyingBalance; // Update allocated assets strategies[withdrawalQueue[i]].allocated = strategyData.allocated - uint120(withdrawAmount); totalAllocated -= withdrawAmount; + // update assetsRetrieved + assetsRetrieved += withdrawAmount; + // Do actual withdraw from strategy strategy.withdraw(withdrawAmount, address(this), address(this)); } From c60a2e6a5c24f86a3a8b89fc968530979c4f5d26 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 3 May 2024 12:54:05 +0100 Subject: [PATCH 020/316] lint --- src/FourSixTwoSixAgg.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 69768c31..49cae369 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -224,7 +224,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { // update assetsRetrieved assetsRetrieved += withdrawAmount; - + // Do actual withdraw from strategy strategy.withdraw(withdrawAmount, address(this), address(this)); } From dd2cc06cd2593935dd6fa240f27dd3c982ec94c5 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 7 May 2024 15:45:11 +0100 Subject: [PATCH 021/316] test: base deployment tests --- src/FourSixTwoSixAgg.sol | 20 +++++++++-- test/unit/FourSixTwoSixAggBase.t.sol | 52 +++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 49cae369..494f5c96 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -53,11 +53,12 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { ESRSlot internal esrSlot; uint256 internal totalAssetsDeposited; - uint256 totalAllocated; - uint256 totalAllocationPoints; + uint256 public totalAllocated; + uint256 public totalAllocationPoints; + + address[] public withdrawalQueue; mapping(address => Strategy) internal strategies; - address[] withdrawalQueue; struct ESRSlot { uint40 lastInterestUpdate; @@ -120,6 +121,19 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMIN_ROLE); } + /** + * @notice get strategy params + * @param _strategy strategy's address + * @return Strategy struct + */ + function getStrategy(address _strategy) external view returns (Strategy memory) { + return strategies[_strategy]; + } + + function withdrawalQueueLength() external view returns (uint256) { + return withdrawalQueue.length; + } + function totalAssets() public view override returns (uint256) { return totalAssetsDeposited + interestAccrued(); } diff --git a/test/unit/FourSixTwoSixAggBase.t.sol b/test/unit/FourSixTwoSixAggBase.t.sol index 2a102513..0c99f5f6 100644 --- a/test/unit/FourSixTwoSixAggBase.t.sol +++ b/test/unit/FourSixTwoSixAggBase.t.sol @@ -8,6 +8,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { address deployer; address user1; address user2; + address manager; FourSixTwoSixAgg fourSixTwoSixAgg; @@ -18,7 +19,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { user1 = makeAddr("User_1"); user2 = makeAddr("User_2"); - vm.prank(deployer); + vm.startPrank(deployer); fourSixTwoSixAgg = new FourSixTwoSixAgg( evc, address(assetTST), @@ -28,5 +29,54 @@ contract FourSixTwoSixAggBase is EVaultTestBase { new address[](0), new uint256[](0) ); + + // grant admin roles to deployer + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer); + + // grant roles to manager + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); + + vm.stopPrank(); + } + + function testInitialParams() public { + FourSixTwoSixAgg.Strategy memory cashReserve = fourSixTwoSixAgg.getStrategy(address(0)); + + assertEq(cashReserve.allocated, 0); + assertEq(cashReserve.allocationPoints, type(uint120).max); + assertEq(cashReserve.active, true); + + assertEq( + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE()), + fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE() + ); + assertEq( + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE()), + fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE() + ); + assertEq( + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE()), + fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE() + ); + assertEq( + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE()), + fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE() + ); + + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer)); + + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager)); } } From 3a82456ea4bdf077584345c9eb211f0152f57e11 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 7 May 2024 15:45:26 +0100 Subject: [PATCH 022/316] test: addStrategy() unit tests --- test/unit/AddStrategyTest.t.sol | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 test/unit/AddStrategyTest.t.sol diff --git a/test/unit/AddStrategyTest.t.sol b/test/unit/AddStrategyTest.t.sol new file mode 100644 index 00000000..7d2b5b22 --- /dev/null +++ b/test/unit/AddStrategyTest.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {FourSixTwoSixAggBase, FourSixTwoSixAgg} from "./FourSixTwoSixAggBase.t.sol"; + +contract AddStrategyTest is FourSixTwoSixAggBase { + function setUp() public virtual override { + super.setUp(); + } + + function testAddStrategy() public { + uint256 allocationPoints = type(uint120).max; + + assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); + + vm.prank(manager); + fourSixTwoSixAgg.addStrategy(address(eTST), allocationPoints); + + assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints); + assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 1); + } + + function testAddStrategy_FromUnauthorizedAddress() public { + uint256 allocationPoints = type(uint120).max; + + assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); + + vm.prank(deployer); + vm.expectRevert(); + fourSixTwoSixAgg.addStrategy(address(eTST), allocationPoints); + } + + function testAddStrategy_WithInvalidAsset() public { + uint256 allocationPoints = type(uint120).max; + + assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); + + vm.prank(deployer); + vm.expectRevert(); + fourSixTwoSixAgg.addStrategy(address(eTST2), allocationPoints); + } + + function testAddStrategy_AlreadyAddedStrategy() public { + uint256 allocationPoints = type(uint120).max; + + assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); + + vm.prank(manager); + fourSixTwoSixAgg.addStrategy(address(eTST), allocationPoints); + + assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints); + assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 1); + + vm.prank(deployer); + vm.expectRevert(); + fourSixTwoSixAgg.addStrategy(address(eTST), allocationPoints); + } +} From 29dcec2866477ac820f8e1c63b9ece0dd696b91f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 7 May 2024 17:44:38 +0100 Subject: [PATCH 023/316] remove strategy from withdrawalQueue --- src/FourSixTwoSixAgg.sol | 11 ++++++++++- test/unit/AddStrategyTest.t.sol | 15 +++++---------- test/unit/FourSixTwoSixAggBase.t.sol | 5 +++++ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 494f5c96..219bd785 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -432,7 +432,16 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { totalAllocationPoints -= strategies[strategy].allocationPoints; strategies[strategy].allocationPoints = 0; - // TODO remove from withdrawalQueue + // remove from withdrawalQueue + uint256 lastStrategyIndex = withdrawalQueue.length - 1; + for (uint256 i; i <= lastStrategyIndex; ++i) { + if ((withdrawalQueue[i] == strategy) && (i != lastStrategyIndex)) { + (withdrawalQueue[i], withdrawalQueue[lastStrategyIndex]) = + (withdrawalQueue[lastStrategyIndex], withdrawalQueue[i]); + } + + withdrawalQueue.pop(); + } } function interestAccrued() public view returns (uint256) { diff --git a/test/unit/AddStrategyTest.t.sol b/test/unit/AddStrategyTest.t.sol index 7d2b5b22..2691b39a 100644 --- a/test/unit/AddStrategyTest.t.sol +++ b/test/unit/AddStrategyTest.t.sol @@ -13,8 +13,7 @@ contract AddStrategyTest is FourSixTwoSixAggBase { assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); - vm.prank(manager); - fourSixTwoSixAgg.addStrategy(address(eTST), allocationPoints); + _addStrategy(manager, address(eTST), allocationPoints); assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints); assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 1); @@ -25,9 +24,8 @@ contract AddStrategyTest is FourSixTwoSixAggBase { assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); - vm.prank(deployer); vm.expectRevert(); - fourSixTwoSixAgg.addStrategy(address(eTST), allocationPoints); + _addStrategy(deployer, address(eTST), allocationPoints); } function testAddStrategy_WithInvalidAsset() public { @@ -35,9 +33,8 @@ contract AddStrategyTest is FourSixTwoSixAggBase { assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); - vm.prank(deployer); vm.expectRevert(); - fourSixTwoSixAgg.addStrategy(address(eTST2), allocationPoints); + _addStrategy(manager, address(eTST2), allocationPoints); } function testAddStrategy_AlreadyAddedStrategy() public { @@ -45,14 +42,12 @@ contract AddStrategyTest is FourSixTwoSixAggBase { assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); - vm.prank(manager); - fourSixTwoSixAgg.addStrategy(address(eTST), allocationPoints); + _addStrategy(manager, address(eTST), allocationPoints); assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints); assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 1); - vm.prank(deployer); vm.expectRevert(); - fourSixTwoSixAgg.addStrategy(address(eTST), allocationPoints); + _addStrategy(manager, address(eTST), allocationPoints); } } diff --git a/test/unit/FourSixTwoSixAggBase.t.sol b/test/unit/FourSixTwoSixAggBase.t.sol index 0c99f5f6..1a1fab0d 100644 --- a/test/unit/FourSixTwoSixAggBase.t.sol +++ b/test/unit/FourSixTwoSixAggBase.t.sol @@ -79,4 +79,9 @@ contract FourSixTwoSixAggBase is EVaultTestBase { assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager)); } + + function _addStrategy(address from, address strategy, uint256 allocationPoints) internal { + vm.prank(from); + fourSixTwoSixAgg.addStrategy(strategy, allocationPoints); + } } From 7f57dade9f927ed024a13cfd3e3b54fbb1e35350 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 7 May 2024 17:44:54 +0100 Subject: [PATCH 024/316] test: removeStrategy() --- test/unit/RemoveStrategy.t.sol | 42 ++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 test/unit/RemoveStrategy.t.sol diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol new file mode 100644 index 00000000..cb9e2fa6 --- /dev/null +++ b/test/unit/RemoveStrategy.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {FourSixTwoSixAggBase, FourSixTwoSixAgg} from "./FourSixTwoSixAggBase.t.sol"; + +contract RemoveStrategyTest is FourSixTwoSixAggBase { + uint256 strategyAllocationPoints; + + function setUp() public virtual override { + super.setUp(); + + strategyAllocationPoints = type(uint120).max; + _addStrategy(manager, address(eTST), strategyAllocationPoints); + } + + function testRemoveStrategy() public { + uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); + uint256 withdrawalQueueLengthBefore = fourSixTwoSixAgg.withdrawalQueueLength(); + + vm.prank(manager); + fourSixTwoSixAgg.removeStrategy(address(eTST)); + + FourSixTwoSixAgg.Strategy memory strategyAfter = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(strategyAfter.active, false); + assertEq(strategyAfter.allocationPoints, 0); + assertEq(fourSixTwoSixAgg.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); + assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), withdrawalQueueLengthBefore - 1); + } + + function testRemoveStrategy_fromUnauthorized() public { + vm.prank(deployer); + vm.expectRevert(); + fourSixTwoSixAgg.removeStrategy(address(eTST)); + } + + function testRemoveStrategy_AlreadyRemoved() public { + vm.prank(manager); + vm.expectRevert(); + fourSixTwoSixAgg.removeStrategy(address(eTST2)); + } +} From 5d5186c4e9b3e5786237bc8c6fb0904d60281a22 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 8 May 2024 11:23:29 +0100 Subject: [PATCH 025/316] notes review --- src/FourSixTwoSixAgg.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 219bd785..1fffca8b 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -119,6 +119,8 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { _setRoleAdmin(WITHDRAW_QUEUE_REORDERER_ROLE, WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE); _setRoleAdmin(STRATEGY_ADDER_ROLE, STRATEGY_ADDER_ROLE_ADMIN_ROLE); _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMIN_ROLE); + + // Fix: increase totalAllocationPoints } /** @@ -314,6 +316,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { uint256 currentCash = totalAssetsAllocatableCache - totalAllocated; // Calculate available cash to put in strategies + // Fix: cash available calc uint256 cashAvailable; if (targetCash > currentCash) { cashAvailable = targetCash - currentCash; @@ -345,6 +348,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { // Todo possibly allow batch harvest function harvest(address strategy) public nonReentrant { + // Fix: return if strategyData.allocated == 0 Strategy memory strategyData = strategies[strategy]; uint256 sharesBalance = IERC4626(strategy).balanceOf(address(this)); uint256 underlyingBalance = IERC4626(strategy).convertToAssets(sharesBalance); From a6cd8830b8a95e629fde59b8eefbb9e9902f9861 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 9 May 2024 13:50:14 +0300 Subject: [PATCH 026/316] fix: cashAvailable calc --- src/FourSixTwoSixAgg.sol | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 1fffca8b..a6ab4d19 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -316,13 +316,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { uint256 currentCash = totalAssetsAllocatableCache - totalAllocated; // Calculate available cash to put in strategies - // Fix: cash available calc - uint256 cashAvailable; - if (targetCash > currentCash) { - cashAvailable = targetCash - currentCash; - } else { - cashAvailable = 0; - } + uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; uint256 toDeposit = targetAllocation - currentAllocation; if (toDeposit > cashAvailable) { From c8c56b977208ccb07325cce471f3b58dd7947ff5 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 9 May 2024 13:54:03 +0300 Subject: [PATCH 027/316] fix: increase totalAllocationPoints by _initialCashAllocationPoints --- src/FourSixTwoSixAgg.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index a6ab4d19..2ddbeb54 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -120,7 +120,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { _setRoleAdmin(STRATEGY_ADDER_ROLE, STRATEGY_ADDER_ROLE_ADMIN_ROLE); _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMIN_ROLE); - // Fix: increase totalAllocationPoints + totalAllocationPoints += _initialCashAllocationPoints; } /** @@ -342,7 +342,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { // Todo possibly allow batch harvest function harvest(address strategy) public nonReentrant { - // Fix: return if strategyData.allocated == 0 Strategy memory strategyData = strategies[strategy]; uint256 sharesBalance = IERC4626(strategy).balanceOf(address(this)); uint256 underlyingBalance = IERC4626(strategy).convertToAssets(sharesBalance); From b58e59eaf492fd52b9af05e16172044a4fe67fb2 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 9 May 2024 15:32:02 +0300 Subject: [PATCH 028/316] chore: some natspec --- src/FourSixTwoSixAgg.sol | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 2ddbeb54..c20d5fc6 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -51,9 +51,12 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { uint256 public constant INTEREST_SMEAR = 2 weeks; ESRSlot internal esrSlot; + /// @dev total amount of _asset deposited into FourSixTwoSixAgg contract uint256 internal totalAssetsDeposited; + /// @dev total amount of _asset deposited across all strategies. uint256 public totalAllocated; + /// @dev total amount of allocation points across all strategies including the cash reserve. uint256 public totalAllocationPoints; address[] public withdrawalQueue; @@ -140,8 +143,10 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return totalAssetsDeposited + interestAccrued(); } + /// @notice get the total assets allocatable + /// @dev the total assets allocatable is the amount of assets deposited into the aggregator + assets already deposited into strategies + /// @return uint256 total assets function totalAssetsAllocatable() public view returns (uint256) { - // Whatever balance of asset this vault holds + whatever is allocated to strategies return IERC20(asset()).balanceOf(address(this)) + totalAllocated; } @@ -298,7 +303,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { if (currentAllocation > targetAllocation) { // Withdraw - // TODO handle maxWithdraw uint256 toWithdraw = currentAllocation - targetAllocation; uint256 maxWithdraw = IERC4626(strategy).maxWithdraw(address(this)); @@ -307,7 +311,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } IERC4626(strategy).withdraw(toWithdraw, address(this), address(this)); - strategies[strategy].allocated = uint120(targetAllocation); //TODO casting + strategies[strategy].allocated = uint120(targetAllocation); totalAllocated -= toWithdraw; } else if (currentAllocation < targetAllocation) { // Deposit From edd4d7d045aed8b6747a1ed0ca58b54bd49d3c23 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 9 May 2024 15:33:04 +0300 Subject: [PATCH 029/316] fix: when withdrawing in rebalance(), decrease the allocated amount by the actual withdrawn amount --- src/FourSixTwoSixAgg.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index c20d5fc6..0273eec3 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -311,7 +311,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } IERC4626(strategy).withdraw(toWithdraw, address(this), address(this)); - strategies[strategy].allocated = uint120(targetAllocation); + strategies[strategy].allocated = uint120(currentAllocation - toWithdraw); totalAllocated -= toWithdraw; } else if (currentAllocation < targetAllocation) { // Deposit From 034b95dfb1e5b4998cff53556e14fba504cd22e1 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 9 May 2024 15:48:01 +0300 Subject: [PATCH 030/316] test: fix tests --- test/unit/AddStrategyTest.t.sol | 12 ++++++------ test/unit/FourSixTwoSixAggBase.t.sol | 6 ++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/test/unit/AddStrategyTest.t.sol b/test/unit/AddStrategyTest.t.sol index 2691b39a..8dd7d5f9 100644 --- a/test/unit/AddStrategyTest.t.sol +++ b/test/unit/AddStrategyTest.t.sol @@ -9,18 +9,18 @@ contract AddStrategyTest is FourSixTwoSixAggBase { } function testAddStrategy() public { - uint256 allocationPoints = type(uint120).max; + uint256 allocationPoints = 500e18; assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); _addStrategy(manager, address(eTST), allocationPoints); - assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints); + assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints + CASH_RESERVE_ALLOCATION_POINTS); assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 1); } function testAddStrategy_FromUnauthorizedAddress() public { - uint256 allocationPoints = type(uint120).max; + uint256 allocationPoints = 500e18; assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); @@ -29,7 +29,7 @@ contract AddStrategyTest is FourSixTwoSixAggBase { } function testAddStrategy_WithInvalidAsset() public { - uint256 allocationPoints = type(uint120).max; + uint256 allocationPoints = 500e18; assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); @@ -38,13 +38,13 @@ contract AddStrategyTest is FourSixTwoSixAggBase { } function testAddStrategy_AlreadyAddedStrategy() public { - uint256 allocationPoints = type(uint120).max; + uint256 allocationPoints = 500e18; assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); _addStrategy(manager, address(eTST), allocationPoints); - assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints); + assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints + CASH_RESERVE_ALLOCATION_POINTS); assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 1); vm.expectRevert(); diff --git a/test/unit/FourSixTwoSixAggBase.t.sol b/test/unit/FourSixTwoSixAggBase.t.sol index 1a1fab0d..8333a9dd 100644 --- a/test/unit/FourSixTwoSixAggBase.t.sol +++ b/test/unit/FourSixTwoSixAggBase.t.sol @@ -5,6 +5,8 @@ import {EVaultTestBase, TestERC20} from "evk/test/unit/evault/EVaultTestBase.t.s import {FourSixTwoSixAgg} from "../../src/FourSixTwoSixAgg.sol"; contract FourSixTwoSixAggBase is EVaultTestBase { + uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; + address deployer; address user1; address user2; @@ -25,7 +27,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { address(assetTST), "assetTST_Agg", "assetTST_Agg", - type(uint120).max, + CASH_RESERVE_ALLOCATION_POINTS, new address[](0), new uint256[](0) ); @@ -49,7 +51,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { FourSixTwoSixAgg.Strategy memory cashReserve = fourSixTwoSixAgg.getStrategy(address(0)); assertEq(cashReserve.allocated, 0); - assertEq(cashReserve.allocationPoints, type(uint120).max); + assertEq(cashReserve.allocationPoints, CASH_RESERVE_ALLOCATION_POINTS); assertEq(cashReserve.active, true); assertEq( From b6f778627481560acb1bbfbb7c7baaa236f8bbd9 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 9 May 2024 15:53:54 +0300 Subject: [PATCH 031/316] fix --- src/FourSixTwoSixAgg.sol | 2 ++ test/unit/AddStrategyTest.t.sol | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 0273eec3..63d498af 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -112,6 +112,8 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { for (uint256 i; i < _initialStrategies.length; ++i) { strategies[_initialStrategies[i]] = Strategy({allocated: 0, allocationPoints: uint120(_initialStrategiesAllocationPoints[i]), active: true}); + + totalAllocationPoints += _initialStrategiesAllocationPoints[i]; } // Setup DEFAULT_ADMIN diff --git a/test/unit/AddStrategyTest.t.sol b/test/unit/AddStrategyTest.t.sol index 8dd7d5f9..901562f9 100644 --- a/test/unit/AddStrategyTest.t.sol +++ b/test/unit/AddStrategyTest.t.sol @@ -10,12 +10,13 @@ contract AddStrategyTest is FourSixTwoSixAggBase { function testAddStrategy() public { uint256 allocationPoints = 500e18; + uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); _addStrategy(manager, address(eTST), allocationPoints); - assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints + CASH_RESERVE_ALLOCATION_POINTS); + assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 1); } @@ -39,12 +40,13 @@ contract AddStrategyTest is FourSixTwoSixAggBase { function testAddStrategy_AlreadyAddedStrategy() public { uint256 allocationPoints = 500e18; + uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); _addStrategy(manager, address(eTST), allocationPoints); - assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints + CASH_RESERVE_ALLOCATION_POINTS); + assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 1); vm.expectRevert(); From 053c07d09538d27add6e8313c3a81eb98790ae8a Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 13 May 2024 14:39:08 +0300 Subject: [PATCH 032/316] basic fuzzing --- foundry.toml | 9 ++- src/FourSixTwoSixAgg.sol | 4 +- .../FourSixTwoSixAggBase.t.sol | 2 +- .../DepositWithdrawMintBurnFuzzTest.t.sol | 64 +++++++++++++++++++ test/unit/AddStrategyTest.t.sol | 2 +- test/unit/RemoveStrategy.t.sol | 2 +- 6 files changed, 73 insertions(+), 10 deletions(-) rename test/{unit => common}/FourSixTwoSixAggBase.t.sol (97%) create mode 100644 test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol diff --git a/foundry.toml b/foundry.toml index 358eeffd..b625e05f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -26,8 +26,8 @@ seed = '0x3e8' dictionary_weight = 100 include_storage = true include_push_bytes = true -match_test = "Fuzzing" -match_contract = "Fuzzing" +match_test = "Fuzz" +match_contract = "Fuzz" [profile.ci_fuzz] runs = 50000 @@ -37,8 +37,8 @@ seed = '0x3e8' dictionary_weight = 100 include_storage = true include_push_bytes = true -match_test = "Fuzzing" -match_contract = "Fuzzing" +match_test = "Fuzz" +match_contract = "Fuzz" [profile.invariant] runs = 256 @@ -51,7 +51,6 @@ include_push_bytes = true [profile.coverage] via_ir = true -no_match_test = "Fuzzing" no_match_contract = "Script" [profile.coverage.optimizer_details] diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 63d498af..84abe9e8 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -51,9 +51,9 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { uint256 public constant INTEREST_SMEAR = 2 weeks; ESRSlot internal esrSlot; - /// @dev total amount of _asset deposited into FourSixTwoSixAgg contract - uint256 internal totalAssetsDeposited; + /// @dev total amount of _asset deposited into FourSixTwoSixAgg contract + uint256 public totalAssetsDeposited; /// @dev total amount of _asset deposited across all strategies. uint256 public totalAllocated; /// @dev total amount of allocation points across all strategies including the cash reserve. diff --git a/test/unit/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol similarity index 97% rename from test/unit/FourSixTwoSixAggBase.t.sol rename to test/common/FourSixTwoSixAggBase.t.sol index 8333a9dd..8c39c86d 100644 --- a/test/unit/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {EVaultTestBase, TestERC20} from "evk/test/unit/evault/EVaultTestBase.t.sol"; +import {EVaultTestBase, TestERC20, console2} from "evk/test/unit/evault/EVaultTestBase.t.sol"; import {FourSixTwoSixAgg} from "../../src/FourSixTwoSixAgg.sol"; contract FourSixTwoSixAggBase is EVaultTestBase { diff --git a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol new file mode 100644 index 00000000..7f1b97ce --- /dev/null +++ b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {console2, FourSixTwoSixAggBase, FourSixTwoSixAgg} from "../common/FourSixTwoSixAggBase.t.sol"; + +contract DepositWithdrawMintBurnFuzzTest is FourSixTwoSixAggBase { + uint256 constant MAX_ALLOWED = type(uint256).max; + + function setUp() public virtual override { + super.setUp(); + } + + function testFuzz_depositShouldIncreaseTotalSupplyAndBalance(uint256 _assets) public { + // moch the scenario of _assets ownership + assetTST.mint(user1, _assets); + + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + _deposit(user1, _assets); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + _assets); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + _assets); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + _assets); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - _assets); + } + + function testFuzzWithdraw(address _receiver, uint256 _assetsToDeposit, uint256 _assetsToWithdraw, uint256 _timestampAfterDeposit) public { + vm.assume(_receiver != address(0)); + + _assetsToDeposit = bound(_assetsToDeposit, 1, type(uint256).max-1); + _assetsToWithdraw = bound(_assetsToWithdraw, 0, _assetsToDeposit); + _timestampAfterDeposit = bound(_timestampAfterDeposit, 0, 86400); + + // deposit + assetTST.mint(user1, _assetsToDeposit); + _deposit(user1, _assetsToDeposit); + vm.warp(block.timestamp + _timestampAfterDeposit); + + // fuzz partial & full withdraws + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 receiverAssetBalanceBefore = assetTST.balanceOf(_receiver); + + vm.startPrank(user1); + fourSixTwoSixAgg.withdraw(_assetsToWithdraw, _receiver, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore - _assetsToWithdraw); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore - _assetsToWithdraw); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); + assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + _assetsToWithdraw); + } + + function _deposit(address _from, uint256 _assets) private { + vm.startPrank(_from); + assetTST.approve(address(fourSixTwoSixAgg), _assets); + fourSixTwoSixAgg.deposit(_assets, _from); + vm.stopPrank(); + } +} diff --git a/test/unit/AddStrategyTest.t.sol b/test/unit/AddStrategyTest.t.sol index 901562f9..97c9893b 100644 --- a/test/unit/AddStrategyTest.t.sol +++ b/test/unit/AddStrategyTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg} from "./FourSixTwoSixAggBase.t.sol"; +import {FourSixTwoSixAggBase, FourSixTwoSixAgg} from "../common/FourSixTwoSixAggBase.t.sol"; contract AddStrategyTest is FourSixTwoSixAggBase { function setUp() public virtual override { diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index cb9e2fa6..8f9d1d4b 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg} from "./FourSixTwoSixAggBase.t.sol"; +import {FourSixTwoSixAggBase, FourSixTwoSixAgg} from "../common/FourSixTwoSixAggBase.t.sol"; contract RemoveStrategyTest is FourSixTwoSixAggBase { uint256 strategyAllocationPoints; From 5de2f109b0218835cfe1adb745949e337dc70fff Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 13 May 2024 14:42:35 +0300 Subject: [PATCH 033/316] refactor: ci --- .github/workflows/test.yml | 71 +++++--------------------------------- 1 file changed, 9 insertions(+), 62 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e621bc17..4a8972fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: ci +name: Foundry on: push: @@ -7,7 +7,7 @@ on: pull_request: jobs: - build: + build-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -20,69 +20,16 @@ jobs: version: nightly - name: Run foundry build - run: | - forge --version - forge build - id: build - - lint-check: - runs-on: ubuntu-latest - needs: build - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly + run: forge build - name: Run foundry fmt check - run: | - forge fmt --check - id: fmt + run: forge fmt --check - test: - runs-on: ubuntu-latest - needs: lint-check - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - name: Run foundry tests - # --ast tests enables inline configs to work https://github.com/foundry-rs/foundry/issues/7310#issuecomment-1978088200 - run: | - forge test -vv --gas-report --ast - id: test + run: forge test -vv --gas-report --ast - fuzz: - runs-on: ubuntu-latest - needs: lint-check - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - name: Run foundry tests - run: | - FOUNDRY_PROFILE=ci_fuzz forge test -vv - id: fuzz + - name: Run foundry fuzzing + run: FOUNDRY_PROFILE=ci_fuzz forge test -vv - # coverage: - # runs-on: ubuntu-latest - # needs: lint-check - # steps: - # - uses: actions/checkout@v3 - # with: - # submodules: recursive - # - uses: foundry-rs/foundry-toolchain@v1 - # with: - # version: nightly - # - name: Run foundry coverage - # run: | - # FOUNDRY_PROFILE=coverage forge coverage --report summary - # id: coverage \ No newline at end of file + # - name: Run foundry coverage + # run: FOUNDRY_PROFILE=coverage forge coverage --report summary \ No newline at end of file From 25a8ea604e7bb99d45927d5849fae5a7aed97262 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 13 May 2024 14:42:52 +0300 Subject: [PATCH 034/316] chore: lint --- test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol index 7f1b97ce..11c3fc29 100644 --- a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol +++ b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol @@ -27,14 +27,19 @@ contract DepositWithdrawMintBurnFuzzTest is FourSixTwoSixAggBase { assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - _assets); } - function testFuzzWithdraw(address _receiver, uint256 _assetsToDeposit, uint256 _assetsToWithdraw, uint256 _timestampAfterDeposit) public { + function testFuzzWithdraw( + address _receiver, + uint256 _assetsToDeposit, + uint256 _assetsToWithdraw, + uint256 _timestampAfterDeposit + ) public { vm.assume(_receiver != address(0)); - _assetsToDeposit = bound(_assetsToDeposit, 1, type(uint256).max-1); + _assetsToDeposit = bound(_assetsToDeposit, 1, type(uint256).max - 1); _assetsToWithdraw = bound(_assetsToWithdraw, 0, _assetsToDeposit); _timestampAfterDeposit = bound(_timestampAfterDeposit, 0, 86400); - // deposit + // deposit assetTST.mint(user1, _assetsToDeposit); _deposit(user1, _assetsToDeposit); vm.warp(block.timestamp + _timestampAfterDeposit); From c013b55fed135fbf06bc45249f954f3b5b269655 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 13 May 2024 14:47:21 +0300 Subject: [PATCH 035/316] refactor: ci --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4a8972fa..cc22c240 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,6 +6,10 @@ on: - main pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: build-and-test: runs-on: ubuntu-latest From db87fadc31ed465eff2fa67be81dff6ac25a07c6 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 13 May 2024 15:11:04 +0300 Subject: [PATCH 036/316] fuzz: mint and redeem --- src/FourSixTwoSixAgg.sol | 4 +- .../DepositWithdrawMintBurnFuzzTest.t.sol | 61 ++++++++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 84abe9e8..e8e77009 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -185,8 +185,8 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return super.deposit(assets, receiver); } - function mint(uint256 assets, address receiver) public override nonReentrant returns (uint256) { - return super.mint(assets, receiver); + function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256) { + return super.mint(shares, receiver); } function withdraw(uint256 assets, address receiver, address owner) diff --git a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol index 11c3fc29..ae9b9efc 100644 --- a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol +++ b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol @@ -10,7 +10,7 @@ contract DepositWithdrawMintBurnFuzzTest is FourSixTwoSixAggBase { super.setUp(); } - function testFuzz_depositShouldIncreaseTotalSupplyAndBalance(uint256 _assets) public { + function testFuzzDeposit(uint256 _assets) public { // moch the scenario of _assets ownership assetTST.mint(user1, _assets); @@ -60,10 +60,69 @@ contract DepositWithdrawMintBurnFuzzTest is FourSixTwoSixAggBase { assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + _assetsToWithdraw); } + function testFuzzMint(uint256 _shares) public { + // moch the scenario of _assets ownership + uint256 assets = fourSixTwoSixAgg.previewMint(_shares); + assetTST.mint(user1, assets); + + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + _mint(user1, assets, _shares); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + _shares); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + _shares); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + assets); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - assets); + } + + function testFuzzRedeem( + address _receiver, + uint256 _sharesToMint, + uint256 _sharesToRedeem, + uint256 _timestampAfterDeposit + ) public { + vm.assume(_receiver != address(0)); + + _sharesToMint = bound(_sharesToMint, 1, type(uint256).max - 1); + _sharesToRedeem = bound(_sharesToRedeem, 0, _sharesToMint); + _timestampAfterDeposit = bound(_timestampAfterDeposit, 0, 86400); + + // deposit + uint256 assetsToDeposit = fourSixTwoSixAgg.previewMint(_sharesToMint); + assetTST.mint(user1, assetsToDeposit); + _mint(user1, assetsToDeposit, _sharesToMint); + vm.warp(block.timestamp + _timestampAfterDeposit); + + // fuzz partial & full redeem + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 receiverAssetBalanceBefore = assetTST.balanceOf(_receiver); + + vm.startPrank(user1); + uint256 assetsToWithdraw = fourSixTwoSixAgg.redeem(_sharesToRedeem, _receiver, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore - _sharesToRedeem); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore - _sharesToRedeem); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); + assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + assetsToWithdraw); + } + function _deposit(address _from, uint256 _assets) private { vm.startPrank(_from); assetTST.approve(address(fourSixTwoSixAgg), _assets); fourSixTwoSixAgg.deposit(_assets, _from); vm.stopPrank(); } + + function _mint(address _from, uint256 _assets, uint256 _shares) private { + vm.startPrank(_from); + assetTST.approve(address(fourSixTwoSixAgg), _assets); + fourSixTwoSixAgg.mint(_shares, _from); + vm.stopPrank(); + } } From d6f0ce2d34eb8f2346902aea98f53979e66f28a2 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 13 May 2024 17:47:58 +0300 Subject: [PATCH 037/316] chore: more natspec --- src/FourSixTwoSixAgg.sol | 95 +++++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 20 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index e8e77009..911f059c 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -2,13 +2,10 @@ pragma solidity ^0.8.0; import {Context} from "openzeppelin-contracts/utils/Context.sol"; -import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol"; -import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; +import {ERC20, IERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; -import {ERC4626} from "openzeppelin-contracts/token/ERC20/extensions/ERC4626.sol"; -import {IERC4626} from "openzeppelin-contracts/interfaces/IERC4626.sol"; -import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; -import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; +import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/ERC4626.sol"; +import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol"; // @note Do NOT use with fee on transfer tokens @@ -37,7 +34,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; uint8 internal constant REENTRANCYLOCK__LOCKED = 2; - // ROLES + // Roles bytes32 public constant ALLOCATION_ADJUSTER_ROLE = keccak256("ALLOCATION_ADJUSTER_ROLE"); bytes32 public constant ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE = keccak256("ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE"); bytes32 public constant WITHDRAW_QUEUE_REORDERER_ROLE = keccak256("WITHDRAW_QUEUE_REORDERER_ROLE"); @@ -52,15 +49,17 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { ESRSlot internal esrSlot; - /// @dev total amount of _asset deposited into FourSixTwoSixAgg contract + /// @dev Total amount of _asset deposited into FourSixTwoSixAgg contract uint256 public totalAssetsDeposited; - /// @dev total amount of _asset deposited across all strategies. + /// @dev Total amount of _asset deposited across all strategies. uint256 public totalAllocated; - /// @dev total amount of allocation points across all strategies including the cash reserve. + /// @dev Total amount of allocation points across all strategies including the cash reserve. uint256 public totalAllocationPoints; + /// @dev An array of strategy addresses to withdraw from address[] public withdrawalQueue; + /// @dev Mapping between strategy address and it's allocation config mapping(address => Strategy) internal strategies; struct ESRSlot { @@ -70,6 +69,10 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { uint8 locked; } + /// @dev A struct that hold a strategy allocation's config + /// allocated: amount of asset deposited into strategy + /// allocationPoints number of points allocated to this strategy + /// active: a boolean to indice if this strategy is active or not struct Strategy { uint120 allocated; uint120 allocationPoints; @@ -84,6 +87,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { evc.requireAccountStatusCheck(account); } + /// @dev Non reentrancy modifier for interest rate updates modifier nonReentrant() { if (esrSlot.locked == REENTRANCYLOCK__LOCKED) revert Reentrancy(); @@ -92,6 +96,14 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { esrSlot.locked = REENTRANCYLOCK__UNLOCKED; } + /// @dev Constructor + /// @param _evc EVC address + /// @param _asset Aggregator's asset address + /// @param _name Aggregator's name + /// @param _symbol Aggregator's symbol + /// @param _initialCashAllocationPoints Initial points to be allocated to the cash reserve + /// @param _initialStrategies An array of initial strategies addresses + /// @param _initialStrategiesAllocationPoints An array of initial strategies allocation points constructor( IEVC _evc, address _asset, @@ -108,6 +120,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { strategies[address(0)] = Strategy({allocated: 0, allocationPoints: uint120(_initialCashAllocationPoints), active: true}); + totalAllocationPoints += _initialCashAllocationPoints; for (uint256 i; i < _initialStrategies.length; ++i) { strategies[_initialStrategies[i]] = @@ -124,23 +137,23 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { _setRoleAdmin(WITHDRAW_QUEUE_REORDERER_ROLE, WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE); _setRoleAdmin(STRATEGY_ADDER_ROLE, STRATEGY_ADDER_ROLE_ADMIN_ROLE); _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMIN_ROLE); - - totalAllocationPoints += _initialCashAllocationPoints; } - /** - * @notice get strategy params - * @param _strategy strategy's address - * @return Strategy struct - */ + /// @notice Get strategy params. + /// @param _strategy strategy's address + /// @return Strategy struct function getStrategy(address _strategy) external view returns (Strategy memory) { return strategies[_strategy]; } + /// @notice Return the withdrawal queue length. + /// @return uint256 length function withdrawalQueueLength() external view returns (uint256) { return withdrawalQueue.length; } + /// @notice Return the total amount of assets deposited, plus the accrued interest. + /// @return uint256 total amount function totalAssets() public view override returns (uint256) { return totalAssetsDeposited + interestAccrued(); } @@ -181,14 +194,18 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return super.transferFrom(from, to, amount); } + /// @dev See {IERC4626-deposit}. function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256) { return super.deposit(assets, receiver); } + /// @dev See {IERC4626-mint}. function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256) { return super.mint(shares, receiver); } + /// @dev See {IERC4626-withdraw}. + /// @dev this function update the accrued interest function withdraw(uint256 assets, address receiver, address owner) public override @@ -201,6 +218,8 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return super.withdraw(assets, receiver, owner); } + /// @dev See {IERC4626-redeem}. + /// @dev this function update the accrued interest function redeem(uint256 shares, address receiver, address owner) public override @@ -213,11 +232,17 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return super.redeem(shares, receiver, owner); } + /// @dev Increate the total assets deposited, and call IERC4626._deposit() + /// @dev See {IERC4626-_deposit}. function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { totalAssetsDeposited += assets; super._deposit(caller, receiver, assets, shares); } + /// @dev Withdraw asset back to the user. + /// @dev See {IERC4626-_withdraw}. + /// @dev if the cash reserve can not cover the amount to withdraw, this function will loop through the strategies + /// to cover the remaining amount. This function will revert if the amount to withdraw is not available function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) internal override @@ -288,6 +313,12 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return esrSlotCache; } + /// @notice Rebalance strategy allocation. + /// @dev This function will first harvest yield, gulps and update interest. + /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. + /// If current allocation is less than target allocation, the aggregator will: + /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash + /// - If all the available cash is greater than the max deposit, deposit the max deposit function rebalance(address strategy) public nonReentrant { if (strategy == address(0)) { return; //nothing to rebalance as this is the cash reserve @@ -346,7 +377,9 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } } - // Todo possibly allow batch harvest + /// ToDo: possibly allow batch harvest + /// @notice Harvest positive yield. + /// @param strategy address of strategy function harvest(address strategy) public nonReentrant { Strategy memory strategyData = strategies[strategy]; uint256 sharesBalance = IERC4626(strategy).balanceOf(address(this)); @@ -366,6 +399,10 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { gulp(); } + /// @notice Adjust a certain strategy's allocation points. + /// @dev Can only be called by an address that have the ALLOCATION_ADJUSTER_ROLE + /// @param strategy address of strategy + /// @param newPoints new strategy's points function adjustAllocationPoints(address strategy, uint256 newPoints) public nonReentrant @@ -388,6 +425,10 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } } + /// @notice Swap two strategies indexes in the withdrawal queue. + /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_REORDERER_ROLE. + /// @param index1 index of first strategy + /// @param index2 index of second strategy function reorderWithdrawalQueue(uint8 index1, uint8 index2) public nonReentrant @@ -406,6 +447,10 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { withdrawalQueue[index2] = temp; } + /// @notice Add new strategy with it's allocation points. + /// @dev Can only be called by an address that have STRATEGY_ADDER_ROLE. + /// @param strategy Address of the strategy + /// @param allocationPoints Strategy's allocation points function addStrategy(address strategy, uint256 allocationPoints) public nonReentrant @@ -425,7 +470,10 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { withdrawalQueue.push(strategy); } - // remove strategy, sets its allocation points to zero. Does not pull funds, `harvest` needs to be called to withdraw + /// @notice Remove strategy and set its allocation points to zero. + /// @dev This function does not pull funds, `harvest()` needs to be called to withdraw + /// @dev Can only be called by an address that have the STRATEGY_REMOVER_ROLE + /// @param strategy Address of the strategy function removeStrategy(address strategy) public nonReentrant onlyRole(STRATEGY_REMOVER_ROLE) { if (!strategies[strategy].active) { revert AlreadyRemoved(); @@ -447,10 +495,15 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } } + /// @notice Return the accrued interest + /// @return uint256 accrued interest function interestAccrued() public view returns (uint256) { return interestAccruedFromCache(esrSlot); } + /// @dev Get accrued interest without updating it. + /// @param esrSlotCache Cached esrSlot + /// @return uint256 accrued interest function interestAccruedFromCache(ESRSlot memory esrSlotCache) internal view returns (uint256) { // If distribution ended, full amount is accrued if (block.timestamp > esrSlotCache.interestSmearEnd) { @@ -469,13 +522,15 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return esrSlotCache.interestLeft * timePassed / totalDuration; } + /// @notice Return the ESRSlot struct + /// @return ESRSlot struct function getESRSlot() public view returns (ESRSlot memory) { return esrSlot; } /// @notice Retrieves the message sender in the context of the EVC. /// @dev This function returns the account on behalf of which the current operation is being performed, which is - /// either msg.sender or the account authenticated by the EVC. + /// either msg.sender or the account authenticated by the EVC. /// @return The address of the message sender. function _msgSender() internal view override (Context, EVCUtil) returns (address) { return EVCUtil._msgSender(); From b7ffb3ce907fd87f3885cec75125d61689be6b7e Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 14 May 2024 11:42:48 +0300 Subject: [PATCH 038/316] fix: adjustAllocationPoints() --- src/FourSixTwoSixAgg.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 911f059c..6d4e34c0 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -418,7 +418,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { strategies[strategy].allocationPoints = uint120(newPoints); if (newPoints > strategyData.allocationPoints) { uint256 diff = newPoints - strategyData.allocationPoints; - totalAllocationPoints + totalAllocationPointsCache + diff; + totalAllocationPoints = totalAllocationPointsCache + diff; } else if (newPoints < strategyData.allocationPoints) { uint256 diff = strategyData.allocationPoints - newPoints; totalAllocationPoints = totalAllocationPointsCache - diff; From a88ae3eee78927dff99d9b103a5957241f4e69c3 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 14 May 2024 12:01:00 +0300 Subject: [PATCH 039/316] fix if condition; refactor and rename var --- src/FourSixTwoSixAgg.sol | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 6d4e34c0..d32ad46d 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -408,21 +408,18 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { nonReentrant onlyRole(ALLOCATION_ADJUSTER_ROLE) { - Strategy memory strategyData = strategies[strategy]; + Strategy memory strategyDataCache = strategies[strategy]; uint256 totalAllocationPointsCache = totalAllocationPoints; - if (strategyData.active = false) { + if (!strategyDataCache.active) { revert InactiveStrategy(); } strategies[strategy].allocationPoints = uint120(newPoints); - if (newPoints > strategyData.allocationPoints) { - uint256 diff = newPoints - strategyData.allocationPoints; - totalAllocationPoints = totalAllocationPointsCache + diff; - } else if (newPoints < strategyData.allocationPoints) { - uint256 diff = strategyData.allocationPoints - newPoints; - totalAllocationPoints = totalAllocationPointsCache - diff; - } + + totalAllocationPoints = (newPoints > strategyDataCache.allocationPoints) + ? totalAllocationPointsCache + (newPoints - strategyDataCache.allocationPoints) + : totalAllocationPointsCache - (strategyDataCache.allocationPoints - newPoints); } /// @notice Swap two strategies indexes in the withdrawal queue. From 0c3ac1a7e2416b9aa1d0e3cb8ab51c40787f0542 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 14 May 2024 12:01:11 +0300 Subject: [PATCH 040/316] test: add unit tests --- test/unit/AdjustAllocationPointsTest.t.sol | 74 ++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 test/unit/AdjustAllocationPointsTest.t.sol diff --git a/test/unit/AdjustAllocationPointsTest.t.sol b/test/unit/AdjustAllocationPointsTest.t.sol new file mode 100644 index 00000000..8b7ae117 --- /dev/null +++ b/test/unit/AdjustAllocationPointsTest.t.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {FourSixTwoSixAggBase, FourSixTwoSixAgg} from "../common/FourSixTwoSixAggBase.t.sol"; + +contract AdjustAllocationsPointsTest is FourSixTwoSixAggBase { + uint256 initialStrategyAllocationPoints = 500e18; + + function setUp() public virtual override { + super.setUp(); + + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + } + + function testAdjustAllocationPoints() public { + uint256 newAllocationPoints = 859e18; + uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); + uint256 withdrawalQueueLengthBefore = fourSixTwoSixAgg.withdrawalQueueLength(); + + vm.prank(manager); + fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), newAllocationPoints); + + FourSixTwoSixAgg.Strategy memory strategy = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq( + fourSixTwoSixAgg.totalAllocationPoints(), + totalAllocationPointsBefore + (newAllocationPoints - initialStrategyAllocationPoints) + ); + assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), withdrawalQueueLengthBefore); + assertEq(strategy.allocationPoints, newAllocationPoints); + } + + function testAdjustAllocationPoints_FromUnauthorizedAddress() public { + uint256 newAllocationPoints = 859e18; + + vm.startPrank(deployer); + vm.expectRevert(); + fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), newAllocationPoints); + vm.stopPrank(); + } + + function testAdjustAllocationPoints_InactiveStrategy() public { + uint256 newAllocationPoints = 859e18; + + vm.startPrank(manager); + vm.expectRevert(FourSixTwoSixAgg.InactiveStrategy.selector); + fourSixTwoSixAgg.adjustAllocationPoints(address(eTST2), newAllocationPoints); + vm.stopPrank(); + } + + // function testAddStrategy_WithInvalidAsset() public { + // uint256 allocationPoints = 500e18; + + // assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); + + // vm.expectRevert(); + // _addStrategy(manager, address(eTST2), allocationPoints); + // } + + // function testAddStrategy_AlreadyAddedStrategy() public { + // uint256 allocationPoints = 500e18; + // uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); + + // assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); + + // _addStrategy(manager, address(eTST), allocationPoints); + + // assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); + // assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 1); + + // vm.expectRevert(); + // _addStrategy(manager, address(eTST), allocationPoints); + // } +} From a51d7dbd9d729145726a8e9a9711626d854113af Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 14 May 2024 12:01:34 +0300 Subject: [PATCH 041/316] chore: lint --- src/FourSixTwoSixAgg.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index d32ad46d..9aa127ac 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -416,7 +416,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } strategies[strategy].allocationPoints = uint120(newPoints); - + totalAllocationPoints = (newPoints > strategyDataCache.allocationPoints) ? totalAllocationPointsCache + (newPoints - strategyDataCache.allocationPoints) : totalAllocationPointsCache - (strategyDataCache.allocationPoints - newPoints); From 0f3b11dddd44ae60004f4cdc4da5ee180d63997a Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 14 May 2024 12:12:47 +0300 Subject: [PATCH 042/316] chore: clean --- test/unit/AdjustAllocationPointsTest.t.sol | 24 ---------------------- 1 file changed, 24 deletions(-) diff --git a/test/unit/AdjustAllocationPointsTest.t.sol b/test/unit/AdjustAllocationPointsTest.t.sol index 8b7ae117..da9ef58c 100644 --- a/test/unit/AdjustAllocationPointsTest.t.sol +++ b/test/unit/AdjustAllocationPointsTest.t.sol @@ -47,28 +47,4 @@ contract AdjustAllocationsPointsTest is FourSixTwoSixAggBase { fourSixTwoSixAgg.adjustAllocationPoints(address(eTST2), newAllocationPoints); vm.stopPrank(); } - - // function testAddStrategy_WithInvalidAsset() public { - // uint256 allocationPoints = 500e18; - - // assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); - - // vm.expectRevert(); - // _addStrategy(manager, address(eTST2), allocationPoints); - // } - - // function testAddStrategy_AlreadyAddedStrategy() public { - // uint256 allocationPoints = 500e18; - // uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); - - // assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); - - // _addStrategy(manager, address(eTST), allocationPoints); - - // assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); - // assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 1); - - // vm.expectRevert(); - // _addStrategy(manager, address(eTST), allocationPoints); - // } } From c46e749a0751e73223c923501acb929f901161c1 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 14 May 2024 12:12:59 +0300 Subject: [PATCH 043/316] test: fuzzing --- .../fuzz/AdjustAllocationPointsFuzzTest.t.sol | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 test/fuzz/AdjustAllocationPointsFuzzTest.t.sol diff --git a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol new file mode 100644 index 00000000..c5415507 --- /dev/null +++ b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {FourSixTwoSixAggBase, FourSixTwoSixAgg} from "../common/FourSixTwoSixAggBase.t.sol"; + +contract AdjustAllocationsPointsFuzzTest is FourSixTwoSixAggBase { + function setUp() public virtual override { + super.setUp(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + } + + function testFuzzAdjustAllocationPoints(uint256 _newAllocationPoints) public { + _newAllocationPoints = bound(_newAllocationPoints, 0, type(uint120).max); + + uint256 strategyAllocationPoints = (fourSixTwoSixAgg.getStrategy(address(eTST))).allocationPoints; + uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); + uint256 withdrawalQueueLengthBefore = fourSixTwoSixAgg.withdrawalQueueLength(); + + vm.prank(manager); + fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), _newAllocationPoints); + + FourSixTwoSixAgg.Strategy memory strategy = fourSixTwoSixAgg.getStrategy(address(eTST)); + + if (_newAllocationPoints < strategyAllocationPoints) { + assertEq( + fourSixTwoSixAgg.totalAllocationPoints(), + totalAllocationPointsBefore - (strategyAllocationPoints - _newAllocationPoints) + ); + } else { + assertEq( + fourSixTwoSixAgg.totalAllocationPoints(), + totalAllocationPointsBefore + (_newAllocationPoints - strategyAllocationPoints) + ); + } + assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), withdrawalQueueLengthBefore); + assertEq(strategy.allocationPoints, _newAllocationPoints); + } +} From 8851afa466a089ccd38e178c73611a4b702c97a0 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 14 May 2024 15:43:39 +0300 Subject: [PATCH 044/316] fix --- src/FourSixTwoSixAgg.sol | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 9aa127ac..ed09ad8e 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -8,6 +8,9 @@ import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/E import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol"; + +import {console2} from "forge-std/Test.sol"; + // @note Do NOT use with fee on transfer tokens // @note Do NOT use with rebasing tokens // @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol @@ -246,7 +249,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) internal override - { + { totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); @@ -258,7 +261,8 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { Strategy memory strategyData = strategies[withdrawalQueue[i]]; IERC4626 strategy = IERC4626(withdrawalQueue[i]); - harvest(address(strategy)); + _harvest(address(strategy)); + _gulp(); uint256 sharesBalance = strategy.balanceOf(address(this)); uint256 underlyingBalance = strategy.convertToAssets(sharesBalance); @@ -285,6 +289,10 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } function gulp() public nonReentrant { + _gulp(); + } + + function _gulp() internal { ESRSlot memory esrSlotCache = updateInterestAndReturnESRSlotCache(); uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft; @@ -324,10 +332,12 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return; //nothing to rebalance as this is the cash reserve } + Strategy memory strategyData = strategies[strategy]; + // Harvest profits, also gulps and updates interest - harvest(strategy); + _harvest(strategy); + _gulp(); - Strategy memory strategyData = strategies[strategy]; uint256 totalAllocationPointsCache = totalAllocationPoints; uint256 totalAssetsAllocatableCache = totalAssetsAllocatable(); uint256 targetAllocation = @@ -381,12 +391,23 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { /// @notice Harvest positive yield. /// @param strategy address of strategy function harvest(address strategy) public nonReentrant { + _harvest(strategy); + + _gulp(); + } + + function _harvest(address strategy) internal { Strategy memory strategyData = strategies[strategy]; + + if (strategyData.allocated == 0) return; + uint256 sharesBalance = IERC4626(strategy).balanceOf(address(this)); uint256 underlyingBalance = IERC4626(strategy).convertToAssets(sharesBalance); - // There's yield! - if (underlyingBalance > strategyData.allocated) { + if (underlyingBalance == strategyData.allocated) { + return; + } else if (underlyingBalance > strategyData.allocated) { + // There's yield! uint256 yield = underlyingBalance - strategyData.allocated; strategies[strategy].allocated = uint120(underlyingBalance); totalAllocated += yield; @@ -395,8 +416,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { // TODO handle losses revert NegativeYield(); } - - gulp(); } /// @notice Adjust a certain strategy's allocation points. From 5b882b71c01e718c98a915a5dfc035ebcaae6c58 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 14 May 2024 15:43:51 +0300 Subject: [PATCH 045/316] test: init e2e tests --- .../e2e/DepositRebalanceWithdrawE2ETest.t.sol | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 test/e2e/DepositRebalanceWithdrawE2ETest.t.sol diff --git a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol new file mode 100644 index 00000000..e5afc1d1 --- /dev/null +++ b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2} from "../common/FourSixTwoSixAggBase.t.sol"; + +contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { + uint256 user1InitialBalance = 100000e18; + + function setUp() public virtual override { + super.setUp(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + + assetTST.mint(user1, user1InitialBalance); + } + + function testSingleStrategy_NoYield() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + } + + vm.warp(block.timestamp + 86400); + // partial withdraw, no need to withdraw from strategy as cash reserve is enough + uint256 amountToWithdraw = 6000e18; + { + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + uint256 strategyShareBalanceBefore = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + + vm.prank(user1); + fourSixTwoSixAgg.withdraw(amountToWithdraw, user1, user1); + + assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), strategyShareBalanceBefore); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); + } + + // full withdraw, will have to withdraw from strategy as cash reserve is not enough + { + amountToWithdraw = amountToDeposit - amountToWithdraw; + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + + vm.prank(user1); + fourSixTwoSixAgg.withdraw(amountToWithdraw, user1, user1); + + assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); + } + } +} From b282b3466b972b2f22a43e8454621ef3789409b4 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 14 May 2024 15:44:05 +0300 Subject: [PATCH 046/316] lint --- src/FourSixTwoSixAgg.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index ed09ad8e..8c6fbc04 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -8,7 +8,6 @@ import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/E import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol"; - import {console2} from "forge-std/Test.sol"; // @note Do NOT use with fee on transfer tokens @@ -249,7 +248,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) internal override - { + { totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); From d18e619c8374350d8cbc59f706b035c404a33db9 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 14 May 2024 20:45:16 +0300 Subject: [PATCH 047/316] test: more tests --- src/FourSixTwoSixAgg.sol | 2 - test/common/FourSixTwoSixAggBase.t.sol | 2 +- .../e2e/DepositRebalanceWithdrawE2ETest.t.sol | 75 ++++++++++++++++++- 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 8c6fbc04..9c143d6e 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -8,8 +8,6 @@ import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/E import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol"; -import {console2} from "forge-std/Test.sol"; - // @note Do NOT use with fee on transfer tokens // @note Do NOT use with rebasing tokens // @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 8c39c86d..1ec3c7d7 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {EVaultTestBase, TestERC20, console2} from "evk/test/unit/evault/EVaultTestBase.t.sol"; +import {EVaultTestBase, TestERC20, console2, EVault} from "evk/test/unit/evault/EVaultTestBase.t.sol"; import {FourSixTwoSixAgg} from "../../src/FourSixTwoSixAgg.sol"; contract FourSixTwoSixAggBase is EVaultTestBase { diff --git a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol index e5afc1d1..d6eb3295 100644 --- a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2} from "../common/FourSixTwoSixAggBase.t.sol"; +import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault} from "../common/FourSixTwoSixAggBase.t.sol"; contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { uint256 user1InitialBalance = 100000e18; @@ -51,7 +51,9 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq( + (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash + ); } vm.warp(block.timestamp + 86400); @@ -71,6 +73,7 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated); } // full withdraw, will have to withdraw from strategy as cash reserve is not enough @@ -88,6 +91,74 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, 0); + } + } + + function testSingleStrategy_WithYield() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + } + + vm.warp(block.timestamp + 86400); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + // mock an increase of strategy balance by 10% + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encode(aggrCurrentStrategyBalance * 11e17 / 1e18) + ); + + // full withdraw, will have to withdraw from strategy as cash reserve is not enough + { + uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + + vm.prank(user1); + fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + vm.clearMockedCalls(); + + assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + fourSixTwoSixAgg.convertToAssets(amountToWithdraw)); } } } From f22d65e703b19fec878d6a9fadc03c87a5953dba Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 14 May 2024 20:45:25 +0300 Subject: [PATCH 048/316] lint --- test/e2e/DepositRebalanceWithdrawE2ETest.t.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol index d6eb3295..dc7d85e9 100644 --- a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol @@ -158,7 +158,10 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0); assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); - assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + fourSixTwoSixAgg.convertToAssets(amountToWithdraw)); + assertEq( + assetTST.balanceOf(user1), + user1AssetTSTBalanceBefore + fourSixTwoSixAgg.convertToAssets(amountToWithdraw) + ); } } } From 039974937b27872cd30bc26d3f6dd203e9a708fe Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 14 May 2024 21:33:20 +0300 Subject: [PATCH 049/316] chore: update config --- .github/workflows/test.yml | 2 +- foundry.toml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cc22c240..6c33e4bc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: run: forge fmt --check - name: Run foundry tests - run: forge test -vv --gas-report --ast + run: FOUNDRY_PROFILE=test forge test -vv --gas-report --ast - name: Run foundry fuzzing run: FOUNDRY_PROFILE=ci_fuzz forge test -vv diff --git a/foundry.toml b/foundry.toml index b625e05f..e199dc18 100644 --- a/foundry.toml +++ b/foundry.toml @@ -18,6 +18,11 @@ quote_style = "double" number_underscore = "preserve" override_spacing = true +[profile.test] +no_match_test = "Fuzz" +no_match_contract = "Fuzz" +gas_reports = ["*"] + [profile.fuzz] runs = 1000 max_local_rejects = 1024 From a1f6ca64cadad12d8fc0feeb2f9d5475fde8ec8d Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 14 May 2024 21:33:32 +0300 Subject: [PATCH 050/316] test: harvest() unit tests --- test/unit/HarvestTest.t.sol | 114 ++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 test/unit/HarvestTest.t.sol diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol new file mode 100644 index 00000000..0f68a5ae --- /dev/null +++ b/test/unit/HarvestTest.t.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault} from "../common/FourSixTwoSixAggBase.t.sol"; + +contract HarvestTest is FourSixTwoSixAggBase { + uint256 user1InitialBalance = 100000e18; + uint256 amountToDeposit = 10000e18; + + function setUp() public virtual override { + super.setUp(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + + assetTST.mint(user1, user1InitialBalance); + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + } + } + + function testHarvest() public { + // no yield increase + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + uint256 totalAllocatedBefore = fourSixTwoSixAgg.totalAllocated(); + + assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) == strategyBefore.allocated); + + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated); + assertEq(fourSixTwoSixAgg.totalAllocated(), totalAllocatedBefore); + + // positive yield + vm.warp(block.timestamp + 86400); + + // mock an increase of strategy balance by 10% + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encode(aggrCurrentStrategyBalance * 11e17 / 1e18) + ); + + assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) > strategyBefore.allocated); + + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + + assertEq( + (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, + eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) + ); + assertEq( + fourSixTwoSixAgg.totalAllocated(), + totalAllocatedBefore + + (eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) - strategyBefore.allocated) + ); + } + + function testHarvestNegativeYield() public { + vm.warp(block.timestamp + 86400); + + // mock an increase of strategy balance by 10% + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encode(aggrCurrentStrategyBalance * 9e17 / 1e18) + ); + + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated); + + vm.startPrank(user1); + vm.expectRevert(FourSixTwoSixAgg.NegativeYield.selector); + fourSixTwoSixAgg.harvest(address(eTST)); + } +} From 0aa32728369cfde139fd49bb5a3f134e86807fa7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 15 May 2024 17:15:08 +0300 Subject: [PATCH 051/316] fix: load strategyData after harvesting --- src/FourSixTwoSixAgg.sol | 46 ++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 9c143d6e..7951b357 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -8,6 +8,8 @@ import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/E import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol"; +import {console2} from "forge-std/Test.sol"; + // @note Do NOT use with fee on transfer tokens // @note Do NOT use with rebasing tokens // @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol @@ -250,23 +252,31 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); - for (uint256 i = 0; i < withdrawalQueue.length; i++) { + for (uint256 i; i < withdrawalQueue.length; i++) { if (assetsRetrieved >= assets) { break; } - Strategy memory strategyData = strategies[withdrawalQueue[i]]; IERC4626 strategy = IERC4626(withdrawalQueue[i]); _harvest(address(strategy)); _gulp(); + Strategy memory strategyData = strategies[withdrawalQueue[i]]; + uint256 sharesBalance = strategy.balanceOf(address(this)); uint256 underlyingBalance = strategy.convertToAssets(sharesBalance); + console2.log("assets", assets); + console2.log("assetsRetrieved", assetsRetrieved); + uint256 desiredAssets = assets - assetsRetrieved; uint256 withdrawAmount = (underlyingBalance >= desiredAssets) ? desiredAssets : underlyingBalance; + console2.log("strategyData.allocated", strategyData.allocated); + console2.log("withdrawAmount", withdrawAmount); + console2.log("totalAllocated", totalAllocated); + // Update allocated assets strategies[withdrawalQueue[i]].allocated = strategyData.allocated - uint120(withdrawAmount); totalAllocated -= withdrawAmount; @@ -318,6 +328,12 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return esrSlotCache; } + function rebalanceMultipleStrategies(address[] calldata _strategies) external nonReentrant { + for (uint256 i; i < _strategies.length; ++i) { + _rebalance(_strategies[i]); + } + } + /// @notice Rebalance strategy allocation. /// @dev This function will first harvest yield, gulps and update interest. /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. @@ -325,16 +341,20 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash /// - If all the available cash is greater than the max deposit, deposit the max deposit function rebalance(address strategy) public nonReentrant { - if (strategy == address(0)) { + _rebalance(strategy); + } + + function _rebalance(address _strategy) internal { + if (_strategy == address(0)) { return; //nothing to rebalance as this is the cash reserve } - Strategy memory strategyData = strategies[strategy]; - // Harvest profits, also gulps and updates interest - _harvest(strategy); + _harvest(_strategy); _gulp(); + Strategy memory strategyData = strategies[_strategy]; + uint256 totalAllocationPointsCache = totalAllocationPoints; uint256 totalAssetsAllocatableCache = totalAssetsAllocatable(); uint256 targetAllocation = @@ -345,13 +365,13 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { // Withdraw uint256 toWithdraw = currentAllocation - targetAllocation; - uint256 maxWithdraw = IERC4626(strategy).maxWithdraw(address(this)); + uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(address(this)); if (toWithdraw > maxWithdraw) { toWithdraw = maxWithdraw; } - IERC4626(strategy).withdraw(toWithdraw, address(this), address(this)); - strategies[strategy].allocated = uint120(currentAllocation - toWithdraw); + IERC4626(_strategy).withdraw(toWithdraw, address(this), address(this)); + strategies[_strategy].allocated = uint120(currentAllocation - toWithdraw); totalAllocated -= toWithdraw; } else if (currentAllocation < targetAllocation) { // Deposit @@ -367,7 +387,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { toDeposit = cashAvailable; } - uint256 maxDeposit = IERC4626(strategy).maxDeposit(address(this)); + uint256 maxDeposit = IERC4626(_strategy).maxDeposit(address(this)); if (toDeposit > maxDeposit) { toDeposit = maxDeposit; } @@ -377,9 +397,9 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } // Do required approval (safely) and deposit - IERC20(asset()).safeApprove(strategy, toDeposit); - IERC4626(strategy).deposit(toDeposit, address(this)); - strategies[strategy].allocated = uint120(currentAllocation + toDeposit); + IERC20(asset()).safeApprove(_strategy, toDeposit); + IERC4626(_strategy).deposit(toDeposit, address(this)); + strategies[_strategy].allocated = uint120(currentAllocation + toDeposit); totalAllocated += toDeposit; } } From 0a30414525dde9513794a37859d963a533da6f11 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 16 May 2024 13:11:24 +0300 Subject: [PATCH 052/316] more tests --- src/FourSixTwoSixAgg.sol | 9 -- test/common/FourSixTwoSixAggBase.t.sol | 2 +- .../e2e/DepositRebalanceWithdrawE2ETest.t.sol | 136 ++++++++++++++++-- 3 files changed, 129 insertions(+), 18 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 7951b357..1200d172 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -8,8 +8,6 @@ import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/E import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol"; -import {console2} from "forge-std/Test.sol"; - // @note Do NOT use with fee on transfer tokens // @note Do NOT use with rebasing tokens // @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol @@ -267,16 +265,9 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { uint256 sharesBalance = strategy.balanceOf(address(this)); uint256 underlyingBalance = strategy.convertToAssets(sharesBalance); - console2.log("assets", assets); - console2.log("assetsRetrieved", assetsRetrieved); - uint256 desiredAssets = assets - assetsRetrieved; uint256 withdrawAmount = (underlyingBalance >= desiredAssets) ? desiredAssets : underlyingBalance; - console2.log("strategyData.allocated", strategyData.allocated); - console2.log("withdrawAmount", withdrawAmount); - console2.log("totalAllocated", totalAllocated); - // Update allocated assets strategies[withdrawalQueue[i]].allocated = strategyData.allocated - uint120(withdrawAmount); totalAllocated -= withdrawAmount; diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 1ec3c7d7..bf3298d2 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {EVaultTestBase, TestERC20, console2, EVault} from "evk/test/unit/evault/EVaultTestBase.t.sol"; +import "evk/test/unit/evault/EVaultTestBase.t.sol"; import {FourSixTwoSixAgg} from "../../src/FourSixTwoSixAgg.sol"; contract FourSixTwoSixAggBase is EVaultTestBase { diff --git a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol index dc7d85e9..029f6b2c 100644 --- a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol @@ -1,7 +1,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault} from "../common/FourSixTwoSixAggBase.t.sol"; +import { + FourSixTwoSixAggBase, + FourSixTwoSixAgg, + console2, + EVault, + IEVault, + IRMTestDefault, + TestERC20 +} from "../common/FourSixTwoSixAggBase.t.sol"; contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { uint256 user1InitialBalance = 100000e18; @@ -135,13 +143,126 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { } vm.warp(block.timestamp + 86400); - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); // mock an increase of strategy balance by 10% - vm.mockCall( - address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), - abi.encode(aggrCurrentStrategyBalance * 11e17 / 1e18) - ); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); + uint256 aggrNewStrategyShareBalance = aggrCurrentStrategyShareBalance * 11e17 / 1e18; + uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; + uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; + assetTST.mint(address(eTST), yield); + eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + + // full withdraw, will have to withdraw from strategy as cash reserve is not enough + { + uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + + vm.prank(user1); + fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + + assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), yield); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq( + assetTST.balanceOf(user1), + user1AssetTSTBalanceBefore + fourSixTwoSixAgg.convertToAssets(amountToWithdraw) + ); + } + } + + function testMultipleStrategy_WithYield() public { + IEVault eTSTsecondary; + { + eTSTsecondary = IEVault(coreProductLine.createVault(address(assetTST), address(oracle), unitOfAccount)); + eTSTsecondary.setInterestRateModel(address(new IRMTestDefault())); + + uint256 initialStrategyAllocationPoints = 1000e18; + _addStrategy(manager, address(eTSTsecondary), initialStrategyAllocationPoints); + } + + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + // 2500 total points; 1000 for reserve(40%), 500(20%) for eTST, 1000(40%) for eTSTsecondary + // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + FourSixTwoSixAgg.Strategy memory eTSTsecondarystrategyBefore = + fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTstrategyBefore.allocated); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + eTSTsecondarystrategyBefore.allocated + ); + + uint256 expectedeTSTStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); + + assertTrue(expectedeTSTStrategyCash != 0); + assertTrue(expectedeTSTsecondaryStrategyCash != 0); + + address[] memory strategiesToRebalance = new address[](2); + strategiesToRebalance[0] = address(eTST); + strategiesToRebalance[1] = address(eTSTsecondary); + vm.prank(user1); + fourSixTwoSixAgg.rebalanceMultipleStrategies(strategiesToRebalance); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + expectedeTSTsecondaryStrategyCash + ); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq( + (fourSixTwoSixAgg.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + ); + assertEq( + assetTST.balanceOf(address(fourSixTwoSixAgg)), + amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) + ); + } + + vm.warp(block.timestamp + 86400); + // mock an increase of aggregator balance due to yield + uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); + uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance); + uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; + uint256 aggrNeweTSTsecondaryUnderlyingBalance = aggrCurrenteTSTsecondaryUnderlyingBalance * 11e17 / 1e18; + uint256 eTSTYield = aggrNeweTSTUnderlyingBalance - aggrCurrenteTSTUnderlyingBalance; + uint256 eTSTsecondaryYield = aggrNeweTSTsecondaryUnderlyingBalance - aggrCurrenteTSTsecondaryUnderlyingBalance; + + assetTST.mint(address(eTST), eTSTYield); + assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield); + eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + eTSTsecondary.skim(type(uint256).max, address(fourSixTwoSixAgg)); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { @@ -153,7 +274,6 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { vm.prank(user1); fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); - vm.clearMockedCalls(); assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0); assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); From 66722469599a68e76f14cd4405cb1eb1bb2fa07b Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 16 May 2024 13:47:48 +0300 Subject: [PATCH 053/316] more tests --- .../e2e/DepositRebalanceWithdrawE2ETest.t.sol | 75 ++++++++++++++++++- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol index 029f6b2c..1405391d 100644 --- a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol @@ -87,7 +87,6 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { // full withdraw, will have to withdraw from strategy as cash reserve is not enough { amountToWithdraw = amountToDeposit - amountToWithdraw; - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); @@ -146,7 +145,6 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { // mock an increase of strategy balance by 10% uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); - uint256 aggrNewStrategyShareBalance = aggrCurrentStrategyShareBalance * 11e17 / 1e18; uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); @@ -155,7 +153,6 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { // full withdraw, will have to withdraw from strategy as cash reserve is not enough { uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); @@ -267,7 +264,6 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { // full withdraw, will have to withdraw from strategy as cash reserve is not enough { uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); @@ -284,4 +280,75 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { ); } } + + function testSingleStrategy_WithYield_WithInterest() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + } + + vm.warp(block.timestamp + 86400); + // mock an increase of strategy balance by 10% + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); + uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; + uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; + assetTST.mint(address(eTST), yield); + eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + + // harvest + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + vm.warp(block.timestamp + 2 weeks); + + // full withdraw, will have to withdraw from strategy as cash reserve is not enough + { + uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + + vm.prank(user1); + fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + + // all yield is distributed + assertApproxEqAbs(eTST.balanceOf(address(fourSixTwoSixAgg)), 0, 1); + assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1); + assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToDeposit + yield, 1); + } + } } From 214303eb774188a842b61a498056ad493b34203b Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 16 May 2024 14:08:28 +0300 Subject: [PATCH 054/316] more tests --- src/FourSixTwoSixAgg.sol | 9 +- .../e2e/DepositRebalanceWithdrawE2ETest.t.sol | 125 ++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 1200d172..4074a31a 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -395,7 +395,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } } - /// ToDo: possibly allow batch harvest /// @notice Harvest positive yield. /// @param strategy address of strategy function harvest(address strategy) public nonReentrant { @@ -404,6 +403,14 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { _gulp(); } + function harvestMultipleStrategies(address[] calldata _strategies) external nonReentrant { + for (uint256 i; i < _strategies.length; ++i) { + _harvest(_strategies[i]); + + _gulp(); + } + } + function _harvest(address strategy) internal { Strategy memory strategyData = strategies[strategy]; diff --git a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol index 1405391d..437b73b6 100644 --- a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol @@ -351,4 +351,129 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToDeposit + yield, 1); } } + + function testMultipleStrategy_WithYield_WithInterest() public { + IEVault eTSTsecondary; + { + eTSTsecondary = IEVault(coreProductLine.createVault(address(assetTST), address(oracle), unitOfAccount)); + eTSTsecondary.setInterestRateModel(address(new IRMTestDefault())); + + uint256 initialStrategyAllocationPoints = 1000e18; + _addStrategy(manager, address(eTSTsecondary), initialStrategyAllocationPoints); + } + + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + // 2500 total points; 1000 for reserve(40%), 500(20%) for eTST, 1000(40%) for eTSTsecondary + // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + FourSixTwoSixAgg.Strategy memory eTSTsecondarystrategyBefore = + fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTstrategyBefore.allocated); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + eTSTsecondarystrategyBefore.allocated + ); + + uint256 expectedeTSTStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); + + assertTrue(expectedeTSTStrategyCash != 0); + assertTrue(expectedeTSTsecondaryStrategyCash != 0); + + address[] memory strategiesToRebalance = new address[](2); + strategiesToRebalance[0] = address(eTST); + strategiesToRebalance[1] = address(eTSTsecondary); + vm.prank(user1); + fourSixTwoSixAgg.rebalanceMultipleStrategies(strategiesToRebalance); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + expectedeTSTsecondaryStrategyCash + ); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq( + (fourSixTwoSixAgg.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + ); + assertEq( + assetTST.balanceOf(address(fourSixTwoSixAgg)), + amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) + ); + } + + vm.warp(block.timestamp + 86400); + uint256 eTSTYield; + uint256 eTSTsecondaryYield; + { + // mock an increase of aggregator balance due to yield + uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); + uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = + eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance); + uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; + uint256 aggrNeweTSTsecondaryUnderlyingBalance = aggrCurrenteTSTsecondaryUnderlyingBalance * 11e17 / 1e18; + eTSTYield = aggrNeweTSTUnderlyingBalance - aggrCurrenteTSTUnderlyingBalance; + eTSTsecondaryYield = aggrNeweTSTsecondaryUnderlyingBalance - aggrCurrenteTSTsecondaryUnderlyingBalance; + + assetTST.mint(address(eTST), eTSTYield); + assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield); + eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + eTSTsecondary.skim(type(uint256).max, address(fourSixTwoSixAgg)); + } + + // harvest + address[] memory strategiesToHarvest = new address[](2); + strategiesToHarvest[0] = address(eTST); + strategiesToHarvest[1] = address(eTSTsecondary); + vm.prank(user1); + fourSixTwoSixAgg.harvestMultipleStrategies(strategiesToHarvest); + vm.warp(block.timestamp + 2 weeks); + + // full withdraw, will have to withdraw from strategy as cash reserve is not enough + { + uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + + vm.prank(user1); + fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + + assertApproxEqAbs(eTST.balanceOf(address(fourSixTwoSixAgg)), 0, 0); + assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1); + assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertApproxEqAbs( + assetTST.balanceOf(user1), + user1AssetTSTBalanceBefore + amountToDeposit + eTSTYield + eTSTsecondaryYield, + 1 + ); + } + } } From dda1ff90a5984f12a7e61b5f2baf54a01fa660d3 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 17 May 2024 12:40:17 +0300 Subject: [PATCH 055/316] remove euler-xyz/euler-vault-kit --- .gitmodules | 3 --- lib/euler-vault-kit | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/euler-vault-kit diff --git a/.gitmodules b/.gitmodules index 0e9f83a5..2e3aabb7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "lib/ethereum-vault-connector"] path = lib/ethereum-vault-connector url = https://github.com/euler-xyz/ethereum-vault-connector -[submodule "lib/euler-vault-kit"] - path = lib/euler-vault-kit - url = https://github.com/euler-xyz/euler-vault-kit diff --git a/lib/euler-vault-kit b/lib/euler-vault-kit deleted file mode 160000 index e7577002..00000000 --- a/lib/euler-vault-kit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e75770023e1b432a660828120cc166b7dc64a222 From e2ce9d514e301c4c46f52cdfa3dc8285066dfe41 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 17 May 2024 12:48:55 +0300 Subject: [PATCH 056/316] forge install: euler-vault-kit --- .gitmodules | 3 +++ lib/euler-vault-kit | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/euler-vault-kit diff --git a/.gitmodules b/.gitmodules index 2e3aabb7..39ce9043 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/ethereum-vault-connector"] path = lib/ethereum-vault-connector url = https://github.com/euler-xyz/ethereum-vault-connector +[submodule "lib/euler-vault-kit"] + path = lib/euler-vault-kit + url = https://github.com/haythemsellami/euler-vault-kit diff --git a/lib/euler-vault-kit b/lib/euler-vault-kit new file mode 160000 index 00000000..5832374a --- /dev/null +++ b/lib/euler-vault-kit @@ -0,0 +1 @@ +Subproject commit 5832374a9f0abd70506cb8ac2a3a324a27043669 From c904793679d66d036de3e5128655dd70750612e9 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 17 May 2024 12:50:03 +0300 Subject: [PATCH 057/316] fix --- .gitmodules | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitmodules b/.gitmodules index 39ce9043..fff97775 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,4 @@ [submodule "lib/euler-vault-kit"] path = lib/euler-vault-kit url = https://github.com/haythemsellami/euler-vault-kit + From 44b52e14bffc9835b68c9d899d3704dcc91ff258 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 17 May 2024 12:57:54 +0300 Subject: [PATCH 058/316] ci: add coverage summary --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6c33e4bc..a04ee8bc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,5 +35,5 @@ jobs: - name: Run foundry fuzzing run: FOUNDRY_PROFILE=ci_fuzz forge test -vv - # - name: Run foundry coverage - # run: FOUNDRY_PROFILE=coverage forge coverage --report summary \ No newline at end of file + - name: Run foundry coverage + run: FOUNDRY_PROFILE=coverage forge coverage --report summary \ No newline at end of file From 15e9b03c6473ddf460617b873641e20d252660f5 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 17 May 2024 13:36:31 +0300 Subject: [PATCH 059/316] add coverage script --- .gitignore | 4 ++++ coverage.sh | 36 ++++++++++++++++++++++++++++++++++++ script/Counter.s.sol | 12 ------------ 3 files changed, 40 insertions(+), 12 deletions(-) create mode 100755 coverage.sh delete mode 100644 script/Counter.s.sol diff --git a/.gitignore b/.gitignore index 85198aaa..565f4937 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ docs/ # Dotenv file .env + +# coverage +lcov.info +coverage \ No newline at end of file diff --git a/coverage.sh b/coverage.sh new file mode 100755 index 00000000..5eca127e --- /dev/null +++ b/coverage.sh @@ -0,0 +1,36 @@ +# Make sure this script is executable. On Unix based systems, run "chmod +x coverage.sh". +# To run this script, type "./coverage.sh". + +#!/bin/bash + +set -e # exit on error + +# check if lcov is installed, if not, execute installation +if ! command -v lcov &>/dev/null; then + echo "lcov is not installed. Installing..." + # check if its macos or linux. + if [ "$(uname)" == "Darwin" ]; then + brew install lcov + + else + sudo apt-get install lcov + fi +fi +lcov --version + +# generates lcov.info +FOUNDRY_PROFILE=coverage forge coverage --report lcov + +# Generate summary +lcov \ + --rc branch_coverage=1 \ + --ignore-errors inconsistent \ + --list lcov.info + +# Open more granular breakdown in browser +genhtml \ + --ignore-errors category \ + --rc branch_coverage=1 \ + --output-directory coverage \ + lcov.info +open coverage/index.html \ No newline at end of file diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index df9ee8b0..00000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; - -contract CounterScript is Script { - function setUp() public {} - - function run() public { - vm.broadcast(); - } -} From d5604db919634a4ba28926a329fcfd8435f742d1 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 17 May 2024 14:15:01 +0300 Subject: [PATCH 060/316] rename --- ...ositRebalanceHarvestWithdrawE2ETest.t.sol} | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) rename test/e2e/{DepositRebalanceWithdrawE2ETest.t.sol => DepositRebalanceHarvestWithdrawE2ETest.t.sol} (86%) diff --git a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol similarity index 86% rename from test/e2e/DepositRebalanceWithdrawE2ETest.t.sol rename to test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 437b73b6..e1a4deac 100644 --- a/test/e2e/DepositRebalanceWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -11,7 +11,7 @@ import { TestERC20 } from "../common/FourSixTwoSixAggBase.t.sol"; -contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { +contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -476,4 +476,75 @@ contract DepositRebalanceWithdrawE2ETest is FourSixTwoSixAggBase { ); } } + + function testSingleStrategy_WithYield_WithInterest() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + } + + vm.warp(block.timestamp + 86400); + // mock an increase of strategy balance by 10% + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); + uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; + uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; + assetTST.mint(address(eTST), yield); + eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + + // harvest + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + vm.warp(block.timestamp + 2 weeks); + + // full withdraw, will have to withdraw from strategy as cash reserve is not enough + { + uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + + vm.prank(user1); + fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + + // all yield is distributed + assertApproxEqAbs(eTST.balanceOf(address(fourSixTwoSixAgg)), 0, 1); + assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1); + assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToDeposit + yield, 1); + } + } } From 14468b1fe635942d94e91e1c821fecc537287f68 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 20 May 2024 13:16:42 +0100 Subject: [PATCH 061/316] add test --- src/FourSixTwoSixAgg.sol | 11 ++- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 98 ++++++++++++++----- 2 files changed, 79 insertions(+), 30 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 4074a31a..8252ee3f 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -8,6 +8,8 @@ import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/E import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol"; +import {console2} from "forge-std/Test.sol"; + // @note Do NOT use with fee on transfer tokens // @note Do NOT use with rebasing tokens // @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol @@ -516,14 +518,17 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { // remove from withdrawalQueue uint256 lastStrategyIndex = withdrawalQueue.length - 1; - for (uint256 i; i <= lastStrategyIndex; ++i) { + + console2.log("lastStrategyIndex", lastStrategyIndex); + + for (uint256 i = 0; i <= lastStrategyIndex; i++) { if ((withdrawalQueue[i] == strategy) && (i != lastStrategyIndex)) { (withdrawalQueue[i], withdrawalQueue[lastStrategyIndex]) = (withdrawalQueue[lastStrategyIndex], withdrawalQueue[i]); } - - withdrawalQueue.pop(); } + + withdrawalQueue.pop(); } /// @notice Return the accrued interest diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index e1a4deac..b06bf03b 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -477,7 +477,16 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { } } - function testSingleStrategy_WithYield_WithInterest() public { + function testWithdraw_NotEnoughAssets() public { + IEVault eTSTsecondary; + { + eTSTsecondary = IEVault(coreProductLine.createVault(address(assetTST), address(oracle), unitOfAccount)); + eTSTsecondary.setInterestRateModel(address(new IRMTestDefault())); + + uint256 initialStrategyAllocationPoints = 1000e18; + _addStrategy(manager, address(eTSTsecondary), initialStrategyAllocationPoints); + } + uint256 amountToDeposit = 10000e18; // deposit into aggregator @@ -499,52 +508,87 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { } // rebalance into strategy + // 2500 total points; 1000 for reserve(40%), 500(20%) for eTST, 1000(40%) for eTSTsecondary + // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + FourSixTwoSixAgg.Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + FourSixTwoSixAgg.Strategy memory eTSTsecondarystrategyBefore = + fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTstrategyBefore.allocated); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + eTSTsecondarystrategyBefore.allocated + ); - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedeTSTStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); + + assertTrue(expectedeTSTStrategyCash != 0); + assertTrue(expectedeTSTsecondaryStrategyCash != 0); + address[] memory strategiesToRebalance = new address[](2); + strategiesToRebalance[0] = address(eTST); + strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + fourSixTwoSixAgg.rebalanceMultipleStrategies(strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + expectedeTSTsecondaryStrategyCash + ); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq( + (fourSixTwoSixAgg.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + ); + assertEq( + assetTST.balanceOf(address(fourSixTwoSixAgg)), + amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) + ); } vm.warp(block.timestamp + 86400); - // mock an increase of strategy balance by 10% - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); - uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); - uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; - uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; - assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + uint256 eTSTYield; + uint256 eTSTsecondaryYield; + { + // mock an increase of aggregator balance due to yield + uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); + uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = + eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance); + uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; + uint256 aggrNeweTSTsecondaryUnderlyingBalance = aggrCurrenteTSTsecondaryUnderlyingBalance * 11e17 / 1e18; + eTSTYield = aggrNeweTSTUnderlyingBalance - aggrCurrenteTSTUnderlyingBalance; + eTSTsecondaryYield = aggrNeweTSTsecondaryUnderlyingBalance - aggrCurrenteTSTsecondaryUnderlyingBalance; + + assetTST.mint(address(eTST), eTSTYield); + assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield); + eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + eTSTsecondary.skim(type(uint256).max, address(fourSixTwoSixAgg)); + } // harvest + address[] memory strategiesToHarvest = new address[](1); + strategiesToHarvest[0] = address(eTST); vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); + fourSixTwoSixAgg.harvestMultipleStrategies(strategiesToHarvest); vm.warp(block.timestamp + 2 weeks); - // full withdraw, will have to withdraw from strategy as cash reserve is not enough + vm.prank(manager); + fourSixTwoSixAgg.removeStrategy(address(eTSTsecondary)); + { uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); + vm.expectRevert(FourSixTwoSixAgg.NotEnoughAssets.selector); fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); - - // all yield is distributed - assertApproxEqAbs(eTST.balanceOf(address(fourSixTwoSixAgg)), 0, 1); - assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1); - assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); - assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToDeposit + yield, 1); } } } From 84f4d127499dc5a7a975e156b6d5e9b8b84b28cd Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 20 May 2024 13:19:23 +0100 Subject: [PATCH 062/316] remove the forked dependency --- .gitmodules | 4 ---- lib/euler-vault-kit | 1 - 2 files changed, 5 deletions(-) delete mode 160000 lib/euler-vault-kit diff --git a/.gitmodules b/.gitmodules index fff97775..2e3aabb7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,7 +4,3 @@ [submodule "lib/ethereum-vault-connector"] path = lib/ethereum-vault-connector url = https://github.com/euler-xyz/ethereum-vault-connector -[submodule "lib/euler-vault-kit"] - path = lib/euler-vault-kit - url = https://github.com/haythemsellami/euler-vault-kit - diff --git a/lib/euler-vault-kit b/lib/euler-vault-kit deleted file mode 160000 index 5832374a..00000000 --- a/lib/euler-vault-kit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5832374a9f0abd70506cb8ac2a3a324a27043669 From aa48e780b81cc8295e8e5e3edcc47877c1166254 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 20 May 2024 13:27:35 +0100 Subject: [PATCH 063/316] forge install: euler-vault-kit --- .gitmodules | 3 +++ lib/euler-vault-kit | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/euler-vault-kit diff --git a/.gitmodules b/.gitmodules index 2e3aabb7..0e9f83a5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/ethereum-vault-connector"] path = lib/ethereum-vault-connector url = https://github.com/euler-xyz/ethereum-vault-connector +[submodule "lib/euler-vault-kit"] + path = lib/euler-vault-kit + url = https://github.com/euler-xyz/euler-vault-kit diff --git a/lib/euler-vault-kit b/lib/euler-vault-kit new file mode 160000 index 00000000..8d8aec00 --- /dev/null +++ b/lib/euler-vault-kit @@ -0,0 +1 @@ +Subproject commit 8d8aec00723daa1cbb723d6f213944199d4bc26b From b4d29a6df82ca68cb92a457e4d13d49254269d9d Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 20 May 2024 13:31:14 +0100 Subject: [PATCH 064/316] use factory instead of product line --- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index b06bf03b..2da74da6 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -173,8 +173,14 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { function testMultipleStrategy_WithYield() public { IEVault eTSTsecondary; { - eTSTsecondary = IEVault(coreProductLine.createVault(address(assetTST), address(oracle), unitOfAccount)); + eTSTsecondary = IEVault( + factory.createProxy( + address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount) + ) + ); eTSTsecondary.setInterestRateModel(address(new IRMTestDefault())); + eTSTsecondary.setMaxLiquidationDiscount(0.2e4); + eTSTsecondary.setFeeReceiver(feeReceiver); uint256 initialStrategyAllocationPoints = 1000e18; _addStrategy(manager, address(eTSTsecondary), initialStrategyAllocationPoints); @@ -355,8 +361,14 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { function testMultipleStrategy_WithYield_WithInterest() public { IEVault eTSTsecondary; { - eTSTsecondary = IEVault(coreProductLine.createVault(address(assetTST), address(oracle), unitOfAccount)); + eTSTsecondary = IEVault( + factory.createProxy( + address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount) + ) + ); eTSTsecondary.setInterestRateModel(address(new IRMTestDefault())); + eTSTsecondary.setMaxLiquidationDiscount(0.2e4); + eTSTsecondary.setFeeReceiver(feeReceiver); uint256 initialStrategyAllocationPoints = 1000e18; _addStrategy(manager, address(eTSTsecondary), initialStrategyAllocationPoints); @@ -480,8 +492,14 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { function testWithdraw_NotEnoughAssets() public { IEVault eTSTsecondary; { - eTSTsecondary = IEVault(coreProductLine.createVault(address(assetTST), address(oracle), unitOfAccount)); + eTSTsecondary = IEVault( + factory.createProxy( + address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount) + ) + ); eTSTsecondary.setInterestRateModel(address(new IRMTestDefault())); + eTSTsecondary.setMaxLiquidationDiscount(0.2e4); + eTSTsecondary.setFeeReceiver(feeReceiver); uint256 initialStrategyAllocationPoints = 1000e18; _addStrategy(manager, address(eTSTsecondary), initialStrategyAllocationPoints); From 323a31ef5534fb7a26056bc917c121a4d8f06530 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 20 May 2024 13:31:49 +0100 Subject: [PATCH 065/316] remove dependency --- .gitmodules | 3 --- lib/euler-vault-kit | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/euler-vault-kit diff --git a/.gitmodules b/.gitmodules index 0e9f83a5..2e3aabb7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "lib/ethereum-vault-connector"] path = lib/ethereum-vault-connector url = https://github.com/euler-xyz/ethereum-vault-connector -[submodule "lib/euler-vault-kit"] - path = lib/euler-vault-kit - url = https://github.com/euler-xyz/euler-vault-kit diff --git a/lib/euler-vault-kit b/lib/euler-vault-kit deleted file mode 160000 index 8d8aec00..00000000 --- a/lib/euler-vault-kit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8d8aec00723daa1cbb723d6f213944199d4bc26b From 096e5724b5668f8c961e703678dc520790cbfa4a Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 20 May 2024 13:32:12 +0100 Subject: [PATCH 066/316] remove dependency --- .gitmodules | 3 --- lib/ethereum-vault-connector | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/ethereum-vault-connector diff --git a/.gitmodules b/.gitmodules index 2e3aabb7..888d42dc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "lib/ethereum-vault-connector"] - path = lib/ethereum-vault-connector - url = https://github.com/euler-xyz/ethereum-vault-connector diff --git a/lib/ethereum-vault-connector b/lib/ethereum-vault-connector deleted file mode 160000 index 9bc0c17a..00000000 --- a/lib/ethereum-vault-connector +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9bc0c17afd6bed51cd9126d9f3b8fc31614d29a9 From a55e8ebf6265797ff8d0aa1ae421e7f982bf9294 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 20 May 2024 13:34:17 +0100 Subject: [PATCH 067/316] forge install: ethereum-vault-connector --- .gitmodules | 3 +++ lib/ethereum-vault-connector | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/ethereum-vault-connector diff --git a/.gitmodules b/.gitmodules index 888d42dc..2e3aabb7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/ethereum-vault-connector"] + path = lib/ethereum-vault-connector + url = https://github.com/euler-xyz/ethereum-vault-connector diff --git a/lib/ethereum-vault-connector b/lib/ethereum-vault-connector new file mode 160000 index 00000000..f791f94e --- /dev/null +++ b/lib/ethereum-vault-connector @@ -0,0 +1 @@ +Subproject commit f791f94e6e790dd82041908983b57412dc04fb84 From 6494f89e62bf1203c713830f85449aa2e07f1a52 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 20 May 2024 13:39:38 +0100 Subject: [PATCH 068/316] forge install: euler-vault-kit --- .gitmodules | 3 +++ lib/euler-vault-kit | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/euler-vault-kit diff --git a/.gitmodules b/.gitmodules index 2e3aabb7..0e9f83a5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/ethereum-vault-connector"] path = lib/ethereum-vault-connector url = https://github.com/euler-xyz/ethereum-vault-connector +[submodule "lib/euler-vault-kit"] + path = lib/euler-vault-kit + url = https://github.com/euler-xyz/euler-vault-kit diff --git a/lib/euler-vault-kit b/lib/euler-vault-kit new file mode 160000 index 00000000..8d8aec00 --- /dev/null +++ b/lib/euler-vault-kit @@ -0,0 +1 @@ +Subproject commit 8d8aec00723daa1cbb723d6f213944199d4bc26b From 7efbaac4f445e0b4be1989f7f2b0473f4166e912 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 20 May 2024 14:34:22 +0100 Subject: [PATCH 069/316] update --- .gitmodules | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitmodules b/.gitmodules index 0e9f83a5..1d3068ab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,4 @@ [submodule "lib/euler-vault-kit"] path = lib/euler-vault-kit url = https://github.com/euler-xyz/euler-vault-kit + From 469dac21998fe589e49adcb55e5c5fed5e3d63f8 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 20 May 2024 14:57:12 +0100 Subject: [PATCH 070/316] clean --- src/FourSixTwoSixAgg.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 8252ee3f..55664699 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -8,8 +8,6 @@ import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/E import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol"; -import {console2} from "forge-std/Test.sol"; - // @note Do NOT use with fee on transfer tokens // @note Do NOT use with rebasing tokens // @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol @@ -519,8 +517,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { // remove from withdrawalQueue uint256 lastStrategyIndex = withdrawalQueue.length - 1; - console2.log("lastStrategyIndex", lastStrategyIndex); - for (uint256 i = 0; i <= lastStrategyIndex; i++) { if ((withdrawalQueue[i] == strategy) && (i != lastStrategyIndex)) { (withdrawalQueue[i], withdrawalQueue[lastStrategyIndex]) = From c04ab49a89220d25fbc1633a6a969f1758de77aa Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 20 May 2024 18:36:38 +0100 Subject: [PATCH 071/316] more tests --- src/FourSixTwoSixAgg.sol | 4 +- test/unit/RebalanceTest.t.sol | 324 +++++++++++++++++++++ test/unit/RemoveStrategy.t.sol | 26 +- test/unit/ReorderWithdrawalQueueTest.t.sol | 61 ++++ 4 files changed, 410 insertions(+), 5 deletions(-) create mode 100644 test/unit/RebalanceTest.t.sol create mode 100644 test/unit/ReorderWithdrawalQueueTest.t.sol diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 55664699..cdba05dd 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -473,9 +473,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { revert SameIndexes(); } - address temp = withdrawalQueue[index1]; - withdrawalQueue[index1] = withdrawalQueue[index2]; - withdrawalQueue[index2] = temp; + (withdrawalQueue[index1], withdrawalQueue[index2]) = (withdrawalQueue[index2], withdrawalQueue[index1]); } /// @notice Add new strategy with it's allocation points. diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol new file mode 100644 index 00000000..c4a8ba97 --- /dev/null +++ b/test/unit/RebalanceTest.t.sol @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + FourSixTwoSixAggBase, + FourSixTwoSixAgg, + console2, + EVault, + IEVault, + IRMTestDefault, + TestERC20 +} from "../common/FourSixTwoSixAggBase.t.sol"; + +contract RebalanceTest is FourSixTwoSixAggBase { + uint256 user1InitialBalance = 100000e18; + + function setUp() public virtual override { + super.setUp(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + + assetTST.mint(user1, user1InitialBalance); + } + + function testRebalanceByDepositing() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq( + (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash + ); + } + + function testRebalanceByDepositingWhenToDepositIsGreaterThanMaxDeposit() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedToDeposit = expectedStrategyCash - strategyBefore.allocated; + uint256 eTSTMaxDeposit = expectedToDeposit * 7e17 / 1e18; + // mock max deposit + vm.mockCall( + address(eTST), abi.encodeCall(eTST.maxDeposit, (address(fourSixTwoSixAgg))), abi.encode(eTSTMaxDeposit) + ); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), eTSTMaxDeposit); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTMaxDeposit); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated + eTSTMaxDeposit); + } + + function testRebalanceByDepositingWhenToDepositIsGreaterThanCashAvailable() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into first strategy + vm.warp(block.timestamp + 86400); + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + // create new strategy & add it + IEVault eTSTsecondary; + uint256 eTSTsecondaryAllocationPoints = 1500e18; + { + eTSTsecondary = IEVault( + factory.createProxy( + address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount) + ) + ); + _addStrategy(manager, address(eTSTsecondary), eTSTsecondaryAllocationPoints); + } + + // rebalance into eTSTsecondary + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); + + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + strategyBefore.allocated + ); + + uint256 targetCash = fourSixTwoSixAgg.totalAssetsAllocatable() + * fourSixTwoSixAgg.getStrategy(address(0)).allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 currentCash = fourSixTwoSixAgg.totalAssetsAllocatable() - fourSixTwoSixAgg.totalAllocated(); + uint256 expectedStrategyCash = currentCash - targetCash; + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTSTsecondary)); + + // assertEq(fourSixTwoSixAgg.totalAllocated(), eTSTsecondaryMaxDeposit); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash + ); + assertEq( + (fourSixTwoSixAgg.getStrategy(address(eTSTsecondary))).allocated, + strategyBefore.allocated + expectedStrategyCash + ); + } + } + + function testRebalanceByDepositingWhenToDepositIsZero() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 eTSTMaxDeposit = 0; + // mock max deposit + vm.mockCall( + address(eTST), abi.encodeCall(eTST.maxDeposit, (address(fourSixTwoSixAgg))), abi.encode(eTSTMaxDeposit) + ); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated); + } + + function testRebalanceByWithdrawing() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + // decrease allocation points + uint256 newAllocationPoints = 300e18; + vm.prank(manager); + fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), newAllocationPoints); + + vm.warp(block.timestamp + 86400); + + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq( + (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, + strategyBefore.allocated - (strategyBefore.allocated - expectedStrategyCash) + ); + } + + function testRebalanceByWithdrawingWhenToWithdrawIsGreaterThanMaxWithdraw() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + // decrease allocation points + uint256 newAllocationPoints = 300e18; + vm.prank(manager); + fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), newAllocationPoints); + + vm.warp(block.timestamp + 86400); + + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedToWithdraw = strategyBefore.allocated - expectedStrategyCash; + uint256 eTSTMaxWithdraw = expectedToWithdraw * 7e17 / 1e18; + // mock max withdraw + vm.mockCall( + address(eTST), abi.encodeCall(eTST.maxWithdraw, (address(fourSixTwoSixAgg))), abi.encode(eTSTMaxWithdraw) + ); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); + assertEq( + eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated - eTSTMaxWithdraw + ); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated - eTSTMaxWithdraw); + } +} diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index 8f9d1d4b..e471c02d 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -1,15 +1,17 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg} from "../common/FourSixTwoSixAggBase.t.sol"; +import {FourSixTwoSixAggBase, FourSixTwoSixAgg, IEVault} from "../common/FourSixTwoSixAggBase.t.sol"; contract RemoveStrategyTest is FourSixTwoSixAggBase { uint256 strategyAllocationPoints; + IEVault anotherStrategy; + function setUp() public virtual override { super.setUp(); - strategyAllocationPoints = type(uint120).max; + strategyAllocationPoints = 1000e18; _addStrategy(manager, address(eTST), strategyAllocationPoints); } @@ -28,6 +30,26 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), withdrawalQueueLengthBefore - 1); } + function testRemoveStrategyWithMultipleStrategies() public { + anotherStrategy = IEVault( + factory.createProxy(address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount)) + ); + _addStrategy(manager, address(anotherStrategy), strategyAllocationPoints); + + uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); + uint256 withdrawalQueueLengthBefore = fourSixTwoSixAgg.withdrawalQueueLength(); + + vm.prank(manager); + fourSixTwoSixAgg.removeStrategy(address(eTST)); + + FourSixTwoSixAgg.Strategy memory strategyAfter = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(strategyAfter.active, false); + assertEq(strategyAfter.allocationPoints, 0); + assertEq(fourSixTwoSixAgg.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); + assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), withdrawalQueueLengthBefore - 1); + } + function testRemoveStrategy_fromUnauthorized() public { vm.prank(deployer); vm.expectRevert(); diff --git a/test/unit/ReorderWithdrawalQueueTest.t.sol b/test/unit/ReorderWithdrawalQueueTest.t.sol new file mode 100644 index 00000000..a68e8341 --- /dev/null +++ b/test/unit/ReorderWithdrawalQueueTest.t.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {FourSixTwoSixAggBase, FourSixTwoSixAgg, IEVault} from "../common/FourSixTwoSixAggBase.t.sol"; + +contract ReorderWithdrawalQueueTest is FourSixTwoSixAggBase { + uint256 eTSTAllocationPoints = 500e18; + uint256 eTSTsecondaryAllocationPoints = 700e18; + + IEVault eTSTsecondary; + + function setUp() public virtual override { + super.setUp(); + + _addStrategy(manager, address(eTST), eTSTAllocationPoints); + + { + eTSTsecondary = IEVault( + factory.createProxy( + address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount) + ) + ); + } + _addStrategy(manager, address(eTSTsecondary), eTSTsecondaryAllocationPoints); + } + + function testReorderWithdrawalQueue() public { + assertEq( + fourSixTwoSixAgg.getStrategy(fourSixTwoSixAgg.withdrawalQueue(0)).allocationPoints, eTSTAllocationPoints + ); + assertEq( + fourSixTwoSixAgg.getStrategy(fourSixTwoSixAgg.withdrawalQueue(1)).allocationPoints, + eTSTsecondaryAllocationPoints + ); + + vm.prank(manager); + fourSixTwoSixAgg.reorderWithdrawalQueue(0, 1); + + assertEq( + fourSixTwoSixAgg.getStrategy(fourSixTwoSixAgg.withdrawalQueue(0)).allocationPoints, + eTSTsecondaryAllocationPoints + ); + assertEq( + fourSixTwoSixAgg.getStrategy(fourSixTwoSixAgg.withdrawalQueue(1)).allocationPoints, eTSTAllocationPoints + ); + } + + function testReorderWithdrawalQueueWhenOutOfBounds() public { + vm.startPrank(manager); + vm.expectRevert(FourSixTwoSixAgg.OutOfBounds.selector); + fourSixTwoSixAgg.reorderWithdrawalQueue(0, 3); + vm.stopPrank(); + } + + function testReorderWithdrawalQueueWhenSameIndex() public { + vm.startPrank(manager); + vm.expectRevert(FourSixTwoSixAgg.SameIndexes.selector); + fourSixTwoSixAgg.reorderWithdrawalQueue(1, 1); + vm.stopPrank(); + } +} From 23258895797693b812afb62440091d07b99c0057 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 20 May 2024 18:54:19 +0100 Subject: [PATCH 072/316] chore: re-order functions --- src/FourSixTwoSixAgg.sol | 336 +++++++++++++++++++-------------------- 1 file changed, 168 insertions(+), 168 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index cdba05dd..758a71e3 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -139,6 +139,130 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMIN_ROLE); } + /// @notice Rebalance strategy allocation. + /// @dev This function will first harvest yield, gulps and update interest. + /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. + /// If current allocation is less than target allocation, the aggregator will: + /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash + /// - If all the available cash is greater than the max deposit, deposit the max deposit + function rebalance(address strategy) public nonReentrant { + _rebalance(strategy); + } + + function rebalanceMultipleStrategies(address[] calldata _strategies) external nonReentrant { + for (uint256 i; i < _strategies.length; ++i) { + _rebalance(_strategies[i]); + } + } + + /// @notice Harvest positive yield. + /// @param strategy address of strategy + function harvest(address strategy) external nonReentrant { + _harvest(strategy); + + _gulp(); + } + + function harvestMultipleStrategies(address[] calldata _strategies) external nonReentrant { + for (uint256 i; i < _strategies.length; ++i) { + _harvest(_strategies[i]); + + _gulp(); + } + } + + /// @notice Adjust a certain strategy's allocation points. + /// @dev Can only be called by an address that have the ALLOCATION_ADJUSTER_ROLE + /// @param strategy address of strategy + /// @param newPoints new strategy's points + function adjustAllocationPoints(address strategy, uint256 newPoints) + external + nonReentrant + onlyRole(ALLOCATION_ADJUSTER_ROLE) + { + Strategy memory strategyDataCache = strategies[strategy]; + uint256 totalAllocationPointsCache = totalAllocationPoints; + + if (!strategyDataCache.active) { + revert InactiveStrategy(); + } + + strategies[strategy].allocationPoints = uint120(newPoints); + + totalAllocationPoints = (newPoints > strategyDataCache.allocationPoints) + ? totalAllocationPointsCache + (newPoints - strategyDataCache.allocationPoints) + : totalAllocationPointsCache - (strategyDataCache.allocationPoints - newPoints); + } + + /// @notice Swap two strategies indexes in the withdrawal queue. + /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_REORDERER_ROLE. + /// @param index1 index of first strategy + /// @param index2 index of second strategy + function reorderWithdrawalQueue(uint8 index1, uint8 index2) + external + nonReentrant + onlyRole(WITHDRAW_QUEUE_REORDERER_ROLE) + { + if (index1 >= withdrawalQueue.length || index2 >= withdrawalQueue.length) { + revert OutOfBounds(); + } + + if (index1 == index2) { + revert SameIndexes(); + } + + (withdrawalQueue[index1], withdrawalQueue[index2]) = (withdrawalQueue[index2], withdrawalQueue[index1]); + } + + /// @notice Add new strategy with it's allocation points. + /// @dev Can only be called by an address that have STRATEGY_ADDER_ROLE. + /// @param strategy Address of the strategy + /// @param allocationPoints Strategy's allocation points + function addStrategy(address strategy, uint256 allocationPoints) + external + nonReentrant + onlyRole(STRATEGY_ADDER_ROLE) + { + if (IERC4626(strategy).asset() != asset()) { + revert InvalidStrategyAsset(); + } + + if (strategies[strategy].active) { + revert StrategyAlreadyExist(); + } + + strategies[strategy] = Strategy({allocated: 0, allocationPoints: uint120(allocationPoints), active: true}); + + totalAllocationPoints += allocationPoints; + withdrawalQueue.push(strategy); + } + + /// @notice Remove strategy and set its allocation points to zero. + /// @dev This function does not pull funds, `harvest()` needs to be called to withdraw + /// @dev Can only be called by an address that have the STRATEGY_REMOVER_ROLE + /// @param strategy Address of the strategy + function removeStrategy(address strategy) external nonReentrant onlyRole(STRATEGY_REMOVER_ROLE) { + if (!strategies[strategy].active) { + revert AlreadyRemoved(); + } + + strategies[strategy].active = false; + totalAllocationPoints -= strategies[strategy].allocationPoints; + strategies[strategy].allocationPoints = 0; + + // remove from withdrawalQueue + uint256 lastStrategyIndex = withdrawalQueue.length - 1; + + for (uint256 i = 0; i <= lastStrategyIndex; i++) { + if ((withdrawalQueue[i] == strategy) && (i != lastStrategyIndex)) { + (withdrawalQueue[i], withdrawalQueue[lastStrategyIndex]) = + (withdrawalQueue[lastStrategyIndex], withdrawalQueue[i]); + } + } + + withdrawalQueue.pop(); + } + /// @notice Get strategy params. /// @param _strategy strategy's address /// @return Strategy struct @@ -152,19 +276,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return withdrawalQueue.length; } - /// @notice Return the total amount of assets deposited, plus the accrued interest. - /// @return uint256 total amount - function totalAssets() public view override returns (uint256) { - return totalAssetsDeposited + interestAccrued(); - } - - /// @notice get the total assets allocatable - /// @dev the total assets allocatable is the amount of assets deposited into the aggregator + assets already deposited into strategies - /// @return uint256 total assets - function totalAssetsAllocatable() public view returns (uint256) { - return IERC20(asset()).balanceOf(address(this)) + totalAllocated; - } - /// @notice Transfers a certain amount of tokens to a recipient. /// @param to The recipient of the transfer. /// @param amount The amount shares to transfer. @@ -232,6 +343,50 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return super.redeem(shares, receiver, owner); } + function gulp() public nonReentrant { + _gulp(); + } + + function updateInterestAndReturnESRSlotCache() public returns (ESRSlot memory) { + ESRSlot memory esrSlotCache = esrSlot; + uint256 accruedInterest = interestAccruedFromCache(esrSlotCache); + + // it's safe to down-cast because the accrued interest is a fraction of interest left + esrSlotCache.interestLeft -= uint168(accruedInterest); + esrSlotCache.lastInterestUpdate = uint40(block.timestamp); + // write esrSlotCache back to storage in a single SSTORE + esrSlot = esrSlotCache; + // Move interest accrued to totalAssetsDeposited + totalAssetsDeposited += accruedInterest; + + return esrSlotCache; + } + + /// @notice Return the total amount of assets deposited, plus the accrued interest. + /// @return uint256 total amount + function totalAssets() public view override returns (uint256) { + return totalAssetsDeposited + interestAccrued(); + } + + /// @notice get the total assets allocatable + /// @dev the total assets allocatable is the amount of assets deposited into the aggregator + assets already deposited into strategies + /// @return uint256 total assets + function totalAssetsAllocatable() public view returns (uint256) { + return IERC20(asset()).balanceOf(address(this)) + totalAllocated; + } + + /// @notice Return the accrued interest + /// @return uint256 accrued interest + function interestAccrued() public view returns (uint256) { + return interestAccruedFromCache(esrSlot); + } + + /// @notice Return the ESRSlot struct + /// @return ESRSlot struct + function getESRSlot() public view returns (ESRSlot memory) { + return esrSlot; + } + /// @dev Increate the total assets deposited, and call IERC4626._deposit() /// @dev See {IERC4626-_deposit}. function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { @@ -286,10 +441,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { super._withdraw(caller, receiver, owner, assets, shares); } - function gulp() public nonReentrant { - _gulp(); - } - function _gulp() internal { ESRSlot memory esrSlotCache = updateInterestAndReturnESRSlotCache(); uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft; @@ -304,37 +455,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { esrSlot = esrSlotCache; } - function updateInterestAndReturnESRSlotCache() public returns (ESRSlot memory) { - ESRSlot memory esrSlotCache = esrSlot; - uint256 accruedInterest = interestAccruedFromCache(esrSlotCache); - - // it's safe to down-cast because the accrued interest is a fraction of interest left - esrSlotCache.interestLeft -= uint168(accruedInterest); - esrSlotCache.lastInterestUpdate = uint40(block.timestamp); - // write esrSlotCache back to storage in a single SSTORE - esrSlot = esrSlotCache; - // Move interest accrued to totalAssetsDeposited - totalAssetsDeposited += accruedInterest; - - return esrSlotCache; - } - - function rebalanceMultipleStrategies(address[] calldata _strategies) external nonReentrant { - for (uint256 i; i < _strategies.length; ++i) { - _rebalance(_strategies[i]); - } - } - - /// @notice Rebalance strategy allocation. - /// @dev This function will first harvest yield, gulps and update interest. - /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. - /// If current allocation is less than target allocation, the aggregator will: - /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash - /// - If all the available cash is greater than the max deposit, deposit the max deposit - function rebalance(address strategy) public nonReentrant { - _rebalance(strategy); - } - function _rebalance(address _strategy) internal { if (_strategy == address(0)) { return; //nothing to rebalance as this is the cash reserve @@ -395,22 +515,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } } - /// @notice Harvest positive yield. - /// @param strategy address of strategy - function harvest(address strategy) public nonReentrant { - _harvest(strategy); - - _gulp(); - } - - function harvestMultipleStrategies(address[] calldata _strategies) external nonReentrant { - for (uint256 i; i < _strategies.length; ++i) { - _harvest(_strategies[i]); - - _gulp(); - } - } - function _harvest(address strategy) internal { Strategy memory strategyData = strategies[strategy]; @@ -433,104 +537,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { } } - /// @notice Adjust a certain strategy's allocation points. - /// @dev Can only be called by an address that have the ALLOCATION_ADJUSTER_ROLE - /// @param strategy address of strategy - /// @param newPoints new strategy's points - function adjustAllocationPoints(address strategy, uint256 newPoints) - public - nonReentrant - onlyRole(ALLOCATION_ADJUSTER_ROLE) - { - Strategy memory strategyDataCache = strategies[strategy]; - uint256 totalAllocationPointsCache = totalAllocationPoints; - - if (!strategyDataCache.active) { - revert InactiveStrategy(); - } - - strategies[strategy].allocationPoints = uint120(newPoints); - - totalAllocationPoints = (newPoints > strategyDataCache.allocationPoints) - ? totalAllocationPointsCache + (newPoints - strategyDataCache.allocationPoints) - : totalAllocationPointsCache - (strategyDataCache.allocationPoints - newPoints); - } - - /// @notice Swap two strategies indexes in the withdrawal queue. - /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_REORDERER_ROLE. - /// @param index1 index of first strategy - /// @param index2 index of second strategy - function reorderWithdrawalQueue(uint8 index1, uint8 index2) - public - nonReentrant - onlyRole(WITHDRAW_QUEUE_REORDERER_ROLE) - { - if (index1 >= withdrawalQueue.length || index2 >= withdrawalQueue.length) { - revert OutOfBounds(); - } - - if (index1 == index2) { - revert SameIndexes(); - } - - (withdrawalQueue[index1], withdrawalQueue[index2]) = (withdrawalQueue[index2], withdrawalQueue[index1]); - } - - /// @notice Add new strategy with it's allocation points. - /// @dev Can only be called by an address that have STRATEGY_ADDER_ROLE. - /// @param strategy Address of the strategy - /// @param allocationPoints Strategy's allocation points - function addStrategy(address strategy, uint256 allocationPoints) - public - nonReentrant - onlyRole(STRATEGY_ADDER_ROLE) - { - if (IERC4626(strategy).asset() != asset()) { - revert InvalidStrategyAsset(); - } - - if (strategies[strategy].active) { - revert StrategyAlreadyExist(); - } - - strategies[strategy] = Strategy({allocated: 0, allocationPoints: uint120(allocationPoints), active: true}); - - totalAllocationPoints += allocationPoints; - withdrawalQueue.push(strategy); - } - - /// @notice Remove strategy and set its allocation points to zero. - /// @dev This function does not pull funds, `harvest()` needs to be called to withdraw - /// @dev Can only be called by an address that have the STRATEGY_REMOVER_ROLE - /// @param strategy Address of the strategy - function removeStrategy(address strategy) public nonReentrant onlyRole(STRATEGY_REMOVER_ROLE) { - if (!strategies[strategy].active) { - revert AlreadyRemoved(); - } - - strategies[strategy].active = false; - totalAllocationPoints -= strategies[strategy].allocationPoints; - strategies[strategy].allocationPoints = 0; - - // remove from withdrawalQueue - uint256 lastStrategyIndex = withdrawalQueue.length - 1; - - for (uint256 i = 0; i <= lastStrategyIndex; i++) { - if ((withdrawalQueue[i] == strategy) && (i != lastStrategyIndex)) { - (withdrawalQueue[i], withdrawalQueue[lastStrategyIndex]) = - (withdrawalQueue[lastStrategyIndex], withdrawalQueue[i]); - } - } - - withdrawalQueue.pop(); - } - - /// @notice Return the accrued interest - /// @return uint256 accrued interest - function interestAccrued() public view returns (uint256) { - return interestAccruedFromCache(esrSlot); - } - /// @dev Get accrued interest without updating it. /// @param esrSlotCache Cached esrSlot /// @return uint256 accrued interest @@ -552,12 +558,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return esrSlotCache.interestLeft * timePassed / totalDuration; } - /// @notice Return the ESRSlot struct - /// @return ESRSlot struct - function getESRSlot() public view returns (ESRSlot memory) { - return esrSlot; - } - /// @notice Retrieves the message sender in the context of the EVC. /// @dev This function returns the account on behalf of which the current operation is being performed, which is /// either msg.sender or the account authenticated by the EVC. From c1356205722aa1aecf775ae31d6a2d466a65352f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 20 May 2024 18:57:10 +0100 Subject: [PATCH 073/316] change to external --- src/FourSixTwoSixAgg.sol | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 758a71e3..6ebeac5a 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -145,7 +145,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { /// If current allocation is less than target allocation, the aggregator will: /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash /// - If all the available cash is greater than the max deposit, deposit the max deposit - function rebalance(address strategy) public nonReentrant { + function rebalance(address strategy) external nonReentrant { _rebalance(strategy); } @@ -263,6 +263,10 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { withdrawalQueue.pop(); } + function gulp() external nonReentrant { + _gulp(); + } + /// @notice Get strategy params. /// @param _strategy strategy's address /// @return Strategy struct @@ -276,6 +280,18 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return withdrawalQueue.length; } + /// @notice Return the ESRSlot struct + /// @return ESRSlot struct + function getESRSlot() external view returns (ESRSlot memory) { + return esrSlot; + } + + /// @notice Return the accrued interest + /// @return uint256 accrued interest + function interestAccrued() external view returns (uint256) { + return interestAccruedFromCache(esrSlot); + } + /// @notice Transfers a certain amount of tokens to a recipient. /// @param to The recipient of the transfer. /// @param amount The amount shares to transfer. @@ -343,10 +359,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return super.redeem(shares, receiver, owner); } - function gulp() public nonReentrant { - _gulp(); - } - function updateInterestAndReturnESRSlotCache() public returns (ESRSlot memory) { ESRSlot memory esrSlotCache = esrSlot; uint256 accruedInterest = interestAccruedFromCache(esrSlotCache); @@ -365,7 +377,7 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { /// @notice Return the total amount of assets deposited, plus the accrued interest. /// @return uint256 total amount function totalAssets() public view override returns (uint256) { - return totalAssetsDeposited + interestAccrued(); + return totalAssetsDeposited + interestAccruedFromCache(esrSlot); } /// @notice get the total assets allocatable @@ -375,18 +387,6 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { return IERC20(asset()).balanceOf(address(this)) + totalAllocated; } - /// @notice Return the accrued interest - /// @return uint256 accrued interest - function interestAccrued() public view returns (uint256) { - return interestAccruedFromCache(esrSlot); - } - - /// @notice Return the ESRSlot struct - /// @return ESRSlot struct - function getESRSlot() public view returns (ESRSlot memory) { - return esrSlot; - } - /// @dev Increate the total assets deposited, and call IERC4626._deposit() /// @dev See {IERC4626-_deposit}. function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { From 409402f162d9b018b95fcbbbec06c0611ea6b798 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 21 May 2024 11:29:59 +0100 Subject: [PATCH 074/316] init: reward streams integration --- src/FourSixTwoSixAgg.sol | 6 ++++-- test/common/FourSixTwoSixAggBase.t.sol | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 6ebeac5a..21523d75 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -7,6 +7,7 @@ import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol" import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/ERC4626.sol"; import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol"; +import {BalanceForwarder} from "./BalanceForwarder.sol"; // @note Do NOT use with fee on transfer tokens // @note Do NOT use with rebasing tokens @@ -14,7 +15,7 @@ import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessContr // @note expired by Yearn v3 ❤️ // TODO addons for reward stream support // TODO custom withdraw queue support -contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { +contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEnumerable { using SafeERC20 for IERC20; error Reentrancy(); @@ -106,13 +107,14 @@ contract FourSixTwoSixAgg is EVCUtil, ERC4626, AccessControlEnumerable { /// @param _initialStrategiesAllocationPoints An array of initial strategies allocation points constructor( IEVC _evc, + address _balanceTracker, address _asset, string memory _name, string memory _symbol, uint256 _initialCashAllocationPoints, address[] memory _initialStrategies, uint256[] memory _initialStrategiesAllocationPoints - ) EVCUtil(address(_evc)) ERC4626(IERC20(_asset)) ERC20(_name, _symbol) { + ) BalanceForwarder(_balanceTracker) EVCUtil(address(_evc)) ERC4626(IERC20(_asset)) ERC20(_name, _symbol) { esrSlot.locked = REENTRANCYLOCK__UNLOCKED; if (_initialStrategies.length != _initialStrategiesAllocationPoints.length) revert ArrayLengthMismatch(); diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index bf3298d2..9730a538 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -24,6 +24,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { vm.startPrank(deployer); fourSixTwoSixAgg = new FourSixTwoSixAgg( evc, + address(0), address(assetTST), "assetTST_Agg", "assetTST_Agg", From a4b1a0f05b20439e3facae1cd7744d739e8efc5f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 21 May 2024 13:32:00 +0100 Subject: [PATCH 075/316] push files --- src/BalanceForwarder.sol | 78 +++++++++++++++++++++++++++++ src/interface/IBalanceForwarder.sol | 12 +++++ 2 files changed, 90 insertions(+) create mode 100644 src/BalanceForwarder.sol create mode 100644 src/interface/IBalanceForwarder.sol diff --git a/src/BalanceForwarder.sol b/src/BalanceForwarder.sol new file mode 100644 index 00000000..25e0450b --- /dev/null +++ b/src/BalanceForwarder.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {IBalanceForwarder} from "./interface/IBalanceForwarder.sol"; + +/// @title BalanceForwarderModule +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice An EVault module handling communication with a balance tracker contract. +abstract contract BalanceForwarder is IBalanceForwarder { + error NotSupported(); + + address public immutable balanceTracker; + + mapping(address => bool) internal isBalanceForwarderEnabled; + + constructor(address _balanceTracker) { + balanceTracker = _balanceTracker; + } + + /// @notice Retrieve the address of rewards contract, tracking changes in account's balances + /// @return The balance tracker address + function balanceTrackerAddress() external view returns (address) { + return balanceTracker; + } + + /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract + /// @param _account Address to query + /// @return True if balance forwarder is enabled + function balanceForwarderEnabled(address _account) external view returns (bool) { + return isBalanceForwarderEnabled[_account]; + } + + /// @notice Enables balance forwarding for the authenticated account + /// @dev Only the authenticated account can enable balance forwarding for itself + /// @dev Should call the IBalanceTracker hook with the current account's balance + function enableBalanceForwarder() external virtual { + _enableBalanceForwarder(msg.sender); + } + + /// @notice Disables balance forwarding for the authenticated account + /// @dev Only the authenticated account can disable balance forwarding for itself + /// @dev Should call the IBalanceTracker hook with the account's balance of 0 + function disableBalanceForwarder() external virtual { + _disableBalanceForwarder(msg.sender); + } + + function _enableBalanceForwarder(address _sender) internal { + if (balanceTracker == address(0)) revert NotSupported(); + + // address account = EVCAuthenticate(); + // UserStorage storage user = vaultStorage.users[account]; + + // bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled(); + + // user.setBalanceForwarder(true); + // balanceTracker.balanceTrackerHook(account, user.getBalance().toUint(), false); + + // if (!wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, true); + } + + /// @notice Disables balance forwarding for the authenticated account + /// @dev Only the authenticated account can disable balance forwarding for itself + /// @dev Should call the IBalanceTracker hook with the account's balance of 0 + function _disableBalanceForwarder(address _sender) internal { + if (balanceTracker == address(0)) revert NotSupported(); + + // address account = EVCAuthenticate(); + // UserStorage storage user = vaultStorage.users[account]; + + // bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled(); + + // user.setBalanceForwarder(false); + // balanceTracker.balanceTrackerHook(account, 0, false); + + // if (wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, false); + } +} diff --git a/src/interface/IBalanceForwarder.sol b/src/interface/IBalanceForwarder.sol new file mode 100644 index 00000000..e88cdae1 --- /dev/null +++ b/src/interface/IBalanceForwarder.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +interface IBalanceForwarder { + function balanceTrackerAddress() external view returns (address); + + function balanceForwarderEnabled(address account) external view returns (bool); + + function enableBalanceForwarder() external; + + function disableBalanceForwarder() external; +} From 03b7c10fd9c73c9aaa725918962f172a90d8ad27 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 21 May 2024 17:49:32 +0100 Subject: [PATCH 076/316] integrate BalanceForwarder --- src/BalanceForwarder.sol | 52 ++++++++++++++++------------------------ src/FourSixTwoSixAgg.sol | 15 ++++++++++++ 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/BalanceForwarder.sol b/src/BalanceForwarder.sol index 25e0450b..dcb4c30c 100644 --- a/src/BalanceForwarder.sol +++ b/src/BalanceForwarder.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; import {IBalanceForwarder} from "./interface/IBalanceForwarder.sol"; +import {IBalanceTracker} from "./interface/IBalanceTracker.sol"; /// @title BalanceForwarderModule /// @custom:security-contact security@euler.xyz @@ -14,10 +15,23 @@ abstract contract BalanceForwarder is IBalanceForwarder { mapping(address => bool) internal isBalanceForwarderEnabled; + event EnableBalanceForwarder(address indexed _user); + event DisableBalanceForwarder(address indexed _user); + constructor(address _balanceTracker) { balanceTracker = _balanceTracker; } + /// @notice Enables balance forwarding for the authenticated account + /// @dev Only the authenticated account can enable balance forwarding for itself + /// @dev Should call the IBalanceTracker hook with the current account's balance + function enableBalanceForwarder() external virtual; + + /// @notice Disables balance forwarding for the authenticated account + /// @dev Only the authenticated account can disable balance forwarding for itself + /// @dev Should call the IBalanceTracker hook with the account's balance of 0 + function disableBalanceForwarder() external virtual; + /// @notice Retrieve the address of rewards contract, tracking changes in account's balances /// @return The balance tracker address function balanceTrackerAddress() external view returns (address) { @@ -31,32 +45,13 @@ abstract contract BalanceForwarder is IBalanceForwarder { return isBalanceForwarderEnabled[_account]; } - /// @notice Enables balance forwarding for the authenticated account - /// @dev Only the authenticated account can enable balance forwarding for itself - /// @dev Should call the IBalanceTracker hook with the current account's balance - function enableBalanceForwarder() external virtual { - _enableBalanceForwarder(msg.sender); - } - - /// @notice Disables balance forwarding for the authenticated account - /// @dev Only the authenticated account can disable balance forwarding for itself - /// @dev Should call the IBalanceTracker hook with the account's balance of 0 - function disableBalanceForwarder() external virtual { - _disableBalanceForwarder(msg.sender); - } - - function _enableBalanceForwarder(address _sender) internal { + function _enableBalanceForwarder(address _sender, uint256 _senderBalance) internal { if (balanceTracker == address(0)) revert NotSupported(); - // address account = EVCAuthenticate(); - // UserStorage storage user = vaultStorage.users[account]; - - // bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled(); - - // user.setBalanceForwarder(true); - // balanceTracker.balanceTrackerHook(account, user.getBalance().toUint(), false); + isBalanceForwarderEnabled[_sender] = true; + IBalanceTracker(balanceTracker).balanceTrackerHook(_sender, _senderBalance, false); - // if (!wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, true); + emit EnableBalanceForwarder(_sender); } /// @notice Disables balance forwarding for the authenticated account @@ -65,14 +60,9 @@ abstract contract BalanceForwarder is IBalanceForwarder { function _disableBalanceForwarder(address _sender) internal { if (balanceTracker == address(0)) revert NotSupported(); - // address account = EVCAuthenticate(); - // UserStorage storage user = vaultStorage.users[account]; - - // bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled(); - - // user.setBalanceForwarder(false); - // balanceTracker.balanceTrackerHook(account, 0, false); + isBalanceForwarderEnabled[_sender] = false; + IBalanceTracker(balanceTracker).balanceTrackerHook(_sender, 0, false); - // if (wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, false); + emit DisableBalanceForwarder(_sender); } } diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 21523d75..0020528a 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -141,6 +141,21 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMIN_ROLE); } + /// @notice Enables balance forwarding for sender + /// @dev Should call the IBalanceTracker hook with the current user's balance + function enableBalanceForwarder() external override nonReentrant { + address user = _msgSender(); + uint256 userBalance = this.balanceOf(user); + + _enableBalanceForwarder(user, userBalance); + } + + /// @notice Disables balance forwarding for the sender + /// @dev Should call the IBalanceTracker hook with the account's balance of 0 + function disableBalanceForwarder() external override nonReentrant { + _disableBalanceForwarder(_msgSender()); + } + /// @notice Rebalance strategy allocation. /// @dev This function will first harvest yield, gulps and update interest. /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. From a883e42eb30f39f3cd74aec278fe5ab9aee42f48 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 21 May 2024 19:50:38 +0100 Subject: [PATCH 077/316] push files --- src/interface/IBalanceTracker.sol | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/interface/IBalanceTracker.sol diff --git a/src/interface/IBalanceTracker.sol b/src/interface/IBalanceTracker.sol new file mode 100644 index 00000000..82a47d7b --- /dev/null +++ b/src/interface/IBalanceTracker.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +/// @title IBalanceTracker +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Provides an interface for tracking the balance of accounts. +interface IBalanceTracker { + /// @notice Executes the balance tracking hook for an account. + /// @dev This function is called by the Balance Forwarder contract which was enabled for the account. This function + /// must be called with the current balance of the account when enabling the balance forwarding for it. This + /// function must be called with 0 balance of the account when disabling the balance forwarding for it. This + /// function allows to be called on zero balance transfers, when the newAccountBalance is the same as the previous + /// one. To prevent DOS attacks, forfeitRecentReward should be used appropriately. + /// @param account The account address to execute the hook for. + /// @param newAccountBalance The new balance of the account. + /// @param forfeitRecentReward Whether to forfeit the most recent reward and not update the accumulator. + function balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external; +} From 9ceb8dbf708d46f8127399bb4b2c117ad55f2358 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 22 May 2024 14:25:39 +0100 Subject: [PATCH 078/316] IBalanceTracker.balanceTrackerHook() calls --- src/BalanceForwarder.sol | 12 ++++++------ src/FourSixTwoSixAgg.sol | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/BalanceForwarder.sol b/src/BalanceForwarder.sol index dcb4c30c..e0e67622 100644 --- a/src/BalanceForwarder.sol +++ b/src/BalanceForwarder.sol @@ -7,11 +7,11 @@ import {IBalanceTracker} from "./interface/IBalanceTracker.sol"; /// @title BalanceForwarderModule /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) -/// @notice An EVault module handling communication with a balance tracker contract. +/// @notice A generic contract to integrate with https://github.com/euler-xyz/reward-streams abstract contract BalanceForwarder is IBalanceForwarder { error NotSupported(); - address public immutable balanceTracker; + IBalanceTracker public immutable balanceTracker; mapping(address => bool) internal isBalanceForwarderEnabled; @@ -19,7 +19,7 @@ abstract contract BalanceForwarder is IBalanceForwarder { event DisableBalanceForwarder(address indexed _user); constructor(address _balanceTracker) { - balanceTracker = _balanceTracker; + balanceTracker = IBalanceTracker(_balanceTracker); } /// @notice Enables balance forwarding for the authenticated account @@ -35,7 +35,7 @@ abstract contract BalanceForwarder is IBalanceForwarder { /// @notice Retrieve the address of rewards contract, tracking changes in account's balances /// @return The balance tracker address function balanceTrackerAddress() external view returns (address) { - return balanceTracker; + return address(balanceTracker); } /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract @@ -46,7 +46,7 @@ abstract contract BalanceForwarder is IBalanceForwarder { } function _enableBalanceForwarder(address _sender, uint256 _senderBalance) internal { - if (balanceTracker == address(0)) revert NotSupported(); + if (address(balanceTracker) == address(0)) revert NotSupported(); isBalanceForwarderEnabled[_sender] = true; IBalanceTracker(balanceTracker).balanceTrackerHook(_sender, _senderBalance, false); @@ -58,7 +58,7 @@ abstract contract BalanceForwarder is IBalanceForwarder { /// @dev Only the authenticated account can disable balance forwarding for itself /// @dev Should call the IBalanceTracker hook with the account's balance of 0 function _disableBalanceForwarder(address _sender) internal { - if (balanceTracker == address(0)) revert NotSupported(); + if (address(balanceTracker) == address(0)) revert NotSupported(); isBalanceForwarderEnabled[_sender] = false; IBalanceTracker(balanceTracker).balanceTrackerHook(_sender, 0, false); diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 0020528a..bee67d47 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -408,6 +408,11 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @dev See {IERC4626-_deposit}. function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { totalAssetsDeposited += assets; + + // if (isBalanceForwarderEnabled[receiver]) { + // balanceTracker.balanceTrackerHook(receiver, shares, false); + // } + super._deposit(caller, receiver, assets, shares); } @@ -455,6 +460,10 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert NotEnoughAssets(); } + // if (isBalanceForwarderEnabled[owner]) { + // balanceTracker.balanceTrackerHook(owner, shares, false); + // } + super._withdraw(caller, receiver, owner, assets, shares); } @@ -554,6 +563,20 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn } } + /// @dev Override _afterTokenTransfer hook to call IBalanceTracker.balanceTrackerHook() + /// @dev Calling .balanceTrackerHook() passing the address total balance + /// @param from Address sending the amount + /// @param to Address receiving the amount + function _afterTokenTransfer(address from, address to, uint256 /*amount*/ ) internal override { + if ((from != address(0)) && (isBalanceForwarderEnabled[from])) { + balanceTracker.balanceTrackerHook(from, super.balanceOf(from), false); + } + + if ((to != address(0)) && (isBalanceForwarderEnabled[to])) { + balanceTracker.balanceTrackerHook(to, super.balanceOf(to), false); + } + } + /// @dev Get accrued interest without updating it. /// @param esrSlotCache Cached esrSlot /// @return uint256 accrued interest From fe4015e01b0d288216b10ad14e78886c94a5d615 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 12:31:34 +0100 Subject: [PATCH 079/316] chore: clean --- src/FourSixTwoSixAgg.sol | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index bee67d47..5c5c3d5a 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -409,10 +409,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { totalAssetsDeposited += assets; - // if (isBalanceForwarderEnabled[receiver]) { - // balanceTracker.balanceTrackerHook(receiver, shares, false); - // } - super._deposit(caller, receiver, assets, shares); } @@ -460,10 +456,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert NotEnoughAssets(); } - // if (isBalanceForwarderEnabled[owner]) { - // balanceTracker.balanceTrackerHook(owner, shares, false); - // } - super._withdraw(caller, receiver, owner, assets, shares); } From cc141af5fda93377d0ddf2bd5d1b979c79e0b034 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 12:33:00 +0100 Subject: [PATCH 080/316] forge install: reward-streams --- .gitmodules | 3 +++ lib/reward-streams | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/reward-streams diff --git a/.gitmodules b/.gitmodules index 1d3068ab..ae5ee093 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,3 +8,6 @@ path = lib/euler-vault-kit url = https://github.com/euler-xyz/euler-vault-kit +[submodule "lib/reward-streams"] + path = lib/reward-streams + url = https://github.com/euler-xyz/reward-streams diff --git a/lib/reward-streams b/lib/reward-streams new file mode 160000 index 00000000..66aafcd3 --- /dev/null +++ b/lib/reward-streams @@ -0,0 +1 @@ +Subproject commit 66aafcd3b00d01b180648f597218ec3c6c67e34a From cea161da6be204a9b32a8e90b7d8c5132dc3b854 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:20:08 +0100 Subject: [PATCH 081/316] update{ --- remappings.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/remappings.txt b/remappings.txt index f65498dc..7fa1974a 100644 --- a/remappings.txt +++ b/remappings.txt @@ -2,6 +2,5 @@ ds-test/=lib/ethereum-vault-connector/lib/forge-std/lib/ds-test/src/ erc4626-tests/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/lib/erc4626-tests/ ethereum-vault-connector/=lib/ethereum-vault-connector/src/ forge-std/=lib/forge-std/src/ -openzeppelin-contracts/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ -openzeppelin/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ evk/=lib/euler-vault-kit/ +reward-streams=lib/reward-streams/src \ No newline at end of file From c47990c017c7e3f41e048e44699b5037822d2f74 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:20:24 +0100 Subject: [PATCH 082/316] add test --- test/e2e/BalanceForwarderE2ETest.t.sol | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 test/e2e/BalanceForwarderE2ETest.t.sol diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol new file mode 100644 index 00000000..257c4c1c --- /dev/null +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + FourSixTwoSixAggBase, + FourSixTwoSixAgg, + console2, + EVault, + IEVault, + IRMTestDefault, + TestERC20 +} from "../common/FourSixTwoSixAggBase.t.sol"; +import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol"; + +contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { + uint256 user1InitialBalance = 100000e18; + + address trackingReward; + + function setUp() public virtual override { + super.setUp(); + + vm.startPrank(deployer); + trackingReward = address(new TrackingRewardStreams(address(evc), 2 weeks)); + + fourSixTwoSixAgg = new FourSixTwoSixAgg( + evc, + trackingReward, + address(assetTST), + "assetTST_Agg", + "assetTST_Agg", + CASH_RESERVE_ALLOCATION_POINTS, + new address[](0), + new uint256[](0) + ); + + // grant admin roles to deployer + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer); + // grant roles to manager + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); + vm.stopPrank(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + + assetTST.mint(user1, user1InitialBalance); + } + + function testBalanceForwarderrAddress_Integrity() public view { + assertEq(fourSixTwoSixAgg.balanceTracker(), trackingReward); + } +} From 6b7a2ba2474d13b4b40439501fc6a3ae29c46ead Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:20:27 +0100 Subject: [PATCH 083/316] forge install: openzeppelin-contracts v5.0.2 --- .gitmodules | 3 +++ lib/openzeppelin-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index ae5ee093..80684906 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ [submodule "lib/reward-streams"] path = lib/reward-streams url = https://github.com/euler-xyz/reward-streams +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 00000000..dbb6104c --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 From 5359ff65b5c4e8a2697bdeb9cb36ac36d0c86a06 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:31:42 +0100 Subject: [PATCH 084/316] update paths --- foundry.toml | 2 +- remappings.txt | 3 ++- src/FourSixTwoSixAgg.sol | 10 +++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/foundry.toml b/foundry.toml index e199dc18..da8c522e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,7 +5,7 @@ libs = ["lib"] test = 'test' optimizer = true optimizer_runs = 20_000 -solc = "0.8.23" +# solc = "0.8.0" gas_reports = ["*"] fs_permissions = [{ access = "read", path = "./"}] diff --git a/remappings.txt b/remappings.txt index 7fa1974a..b33088d0 100644 --- a/remappings.txt +++ b/remappings.txt @@ -3,4 +3,5 @@ erc4626-tests/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/lib/erc46 ethereum-vault-connector/=lib/ethereum-vault-connector/src/ forge-std/=lib/forge-std/src/ evk/=lib/euler-vault-kit/ -reward-streams=lib/reward-streams/src \ No newline at end of file +reward-streams=lib/reward-streams/src +@openzeppelin=lib/openzeppelin-contracts/contracts/ \ No newline at end of file diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 5c5c3d5a..51108d22 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {Context} from "openzeppelin-contracts/utils/Context.sol"; -import {ERC20, IERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; -import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; -import {ERC4626, IERC4626} from "openzeppelin-contracts/token/ERC20/extensions/ERC4626.sol"; +import {Context} from "@openzeppelin/utils/Context.sol"; +import {ERC20, IERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; +import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; +import {ERC4626, IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; +import {AccessControlEnumerable} from "@openzeppelin/access/extensions/AccessControlEnumerable.sol"; import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; -import {AccessControlEnumerable} from "openzeppelin-contracts/access/AccessControlEnumerable.sol"; import {BalanceForwarder} from "./BalanceForwarder.sol"; // @note Do NOT use with fee on transfer tokens From 9eca08ac1cad1c13d1c4718ebe9c8bdee9517ea8 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:32:00 +0100 Subject: [PATCH 085/316] remove dep --- .gitmodules | 3 --- lib/reward-streams | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/reward-streams diff --git a/.gitmodules b/.gitmodules index 80684906..f2d28c20 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,9 +8,6 @@ path = lib/euler-vault-kit url = https://github.com/euler-xyz/euler-vault-kit -[submodule "lib/reward-streams"] - path = lib/reward-streams - url = https://github.com/euler-xyz/reward-streams [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/lib/reward-streams b/lib/reward-streams deleted file mode 160000 index 66aafcd3..00000000 --- a/lib/reward-streams +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 66aafcd3b00d01b180648f597218ec3c6c67e34a From 1dc957a0dea5d7d7b647dd6ff308ebcfc9cfe9c8 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:37:40 +0100 Subject: [PATCH 086/316] forge install: reward-streams --- .gitmodules | 3 +++ lib/reward-streams | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/reward-streams diff --git a/.gitmodules b/.gitmodules index f2d28c20..e7747b03 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "lib/reward-streams"] + path = lib/reward-streams + url = https://github.com/haythemsellami/reward-streams diff --git a/lib/reward-streams b/lib/reward-streams new file mode 160000 index 00000000..8df0a643 --- /dev/null +++ b/lib/reward-streams @@ -0,0 +1 @@ +Subproject commit 8df0a6432580302119c7620365b14c29e725f009 From def1be6e9216fa9c6945a71605cd81c9238c9324 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:48:40 +0100 Subject: [PATCH 087/316] fix setup --- remappings.txt | 3 ++- src/FourSixTwoSixAgg.sol | 2 +- test/e2e/BalanceForwarderE2ETest.t.sol | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/remappings.txt b/remappings.txt index b33088d0..be613868 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,4 +4,5 @@ ethereum-vault-connector/=lib/ethereum-vault-connector/src/ forge-std/=lib/forge-std/src/ evk/=lib/euler-vault-kit/ reward-streams=lib/reward-streams/src -@openzeppelin=lib/openzeppelin-contracts/contracts/ \ No newline at end of file +openzeppelin-contracts/=lib/openzeppelin-contracts/contracts +@openzeppelin/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 51108d22..eea68b59 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -5,7 +5,7 @@ import {Context} from "@openzeppelin/utils/Context.sol"; import {ERC20, IERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; import {ERC4626, IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; -import {AccessControlEnumerable} from "@openzeppelin/access/extensions/AccessControlEnumerable.sol"; +import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumerable.sol"; import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {BalanceForwarder} from "./BalanceForwarder.sol"; diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 257c4c1c..3d7a7bb2 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -53,6 +53,6 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { } function testBalanceForwarderrAddress_Integrity() public view { - assertEq(fourSixTwoSixAgg.balanceTracker(), trackingReward); + assertEq(address(fourSixTwoSixAgg.balanceTracker()), trackingReward); } } From f2f803ea641f4b4cd507e894aad1d47b50a39bff Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:50:32 +0100 Subject: [PATCH 088/316] update --- .gitmodules | 3 --- lib/openzeppelin-contracts | 1 - remappings.txt | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) delete mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index e7747b03..15715476 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,9 +8,6 @@ path = lib/euler-vault-kit url = https://github.com/euler-xyz/euler-vault-kit -[submodule "lib/openzeppelin-contracts"] - path = lib/openzeppelin-contracts - url = https://github.com/OpenZeppelin/openzeppelin-contracts [submodule "lib/reward-streams"] path = lib/reward-streams url = https://github.com/haythemsellami/reward-streams diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts deleted file mode 160000 index dbb6104c..00000000 --- a/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 diff --git a/remappings.txt b/remappings.txt index be613868..1ad598ca 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,5 +4,5 @@ ethereum-vault-connector/=lib/ethereum-vault-connector/src/ forge-std/=lib/forge-std/src/ evk/=lib/euler-vault-kit/ reward-streams=lib/reward-streams/src -openzeppelin-contracts/=lib/openzeppelin-contracts/contracts +openzeppelin-contracts/=lib/reward-streams/lib/openzeppelin-contracts/contracts @openzeppelin/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ From 3829aea1bb84cfb22b0fac390208b840a6aa9985 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:50:58 +0100 Subject: [PATCH 089/316] update --- .gitmodules | 3 --- lib/reward-streams | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/reward-streams diff --git a/.gitmodules b/.gitmodules index 15715476..1d3068ab 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,6 +8,3 @@ path = lib/euler-vault-kit url = https://github.com/euler-xyz/euler-vault-kit -[submodule "lib/reward-streams"] - path = lib/reward-streams - url = https://github.com/haythemsellami/reward-streams diff --git a/lib/reward-streams b/lib/reward-streams deleted file mode 160000 index 8df0a643..00000000 --- a/lib/reward-streams +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8df0a6432580302119c7620365b14c29e725f009 From dc5a20da0145f5b4f90901a6e66b8a8cb99956e3 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 15:52:52 +0100 Subject: [PATCH 090/316] forge install: reward-streams --- .gitmodules | 3 +++ lib/reward-streams | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/reward-streams diff --git a/.gitmodules b/.gitmodules index 1d3068ab..ae5ee093 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,3 +8,6 @@ path = lib/euler-vault-kit url = https://github.com/euler-xyz/euler-vault-kit +[submodule "lib/reward-streams"] + path = lib/reward-streams + url = https://github.com/euler-xyz/reward-streams diff --git a/lib/reward-streams b/lib/reward-streams new file mode 160000 index 00000000..66aafcd3 --- /dev/null +++ b/lib/reward-streams @@ -0,0 +1 @@ +Subproject commit 66aafcd3b00d01b180648f597218ec3c6c67e34a From 7b8f703197bdb7545dec475e4333ccd934e915ee Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 24 May 2024 22:54:33 +0100 Subject: [PATCH 091/316] more tests --- test/e2e/BalanceForwarderE2ETest.t.sol | 121 ++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 3d7a7bb2..d1597e95 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -48,11 +48,130 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { uint256 initialStrategyAllocationPoints = 500e18; _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); - assetTST.mint(user1, user1InitialBalance); + + // deposit into aggregator + uint256 amountToDeposit = 10000e18; + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } } function testBalanceForwarderrAddress_Integrity() public view { assertEq(address(fourSixTwoSixAgg.balanceTracker()), trackingReward); } + + function testEnableBalanceForwarder() public { + vm.prank(user1); + fourSixTwoSixAgg.enableBalanceForwarder(); + + assertTrue(fourSixTwoSixAgg.balanceForwarderEnabled(user1)); + assertEq( + TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), + fourSixTwoSixAgg.balanceOf(user1) + ); + } + + function testDisableBalanceForwarder() public { + vm.prank(user1); + fourSixTwoSixAgg.enableBalanceForwarder(); + + assertTrue(fourSixTwoSixAgg.balanceForwarderEnabled(user1)); + + vm.prank(user1); + fourSixTwoSixAgg.disableBalanceForwarder(); + + assertFalse(fourSixTwoSixAgg.balanceForwarderEnabled(user1)); + assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), 0); + } + + function testHookWhenReceiverEnabled() public { + vm.prank(user1); + fourSixTwoSixAgg.enableBalanceForwarder(); + + // deposit into aggregator + uint256 amountToDeposit = 10000e18; + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + + assertEq( + TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), + fourSixTwoSixAgg.balanceOf(user1) + ); + } + } + + function testHookWhenSenderEnabled() public { + vm.prank(user1); + fourSixTwoSixAgg.enableBalanceForwarder(); + + // deposit into aggregator + uint256 amountToDeposit = 10000e18; + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + + assertEq( + TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), + fourSixTwoSixAgg.balanceOf(user1) + ); + } + + { + uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + + vm.prank(user1); + fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + + assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq( + assetTST.balanceOf(user1), + user1AssetTSTBalanceBefore + fourSixTwoSixAgg.convertToAssets(amountToWithdraw) + ); + assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), 0); + } + } } From 1bb0b873f66f8371d7d73aba558b04a8a7792b1f Mon Sep 17 00:00:00 2001 From: totomanov Date: Mon, 27 May 2024 17:17:36 +0300 Subject: [PATCH 092/316] chore: update dependencies to latest commit --- lib/euler-vault-kit | 2 +- lib/forge-std | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/euler-vault-kit b/lib/euler-vault-kit index 8d8aec00..f6fd0ee3 160000 --- a/lib/euler-vault-kit +++ b/lib/euler-vault-kit @@ -1 +1 @@ -Subproject commit 8d8aec00723daa1cbb723d6f213944199d4bc26b +Subproject commit f6fd0ee3b454630abd961d6471beb0c7eaf1216a diff --git a/lib/forge-std b/lib/forge-std index bb4ceea9..52715a21 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit bb4ceea94d6f10eeb5b41dc2391c6c8bf8e734ef +Subproject commit 52715a217dc51d0de15877878ab8213f6cbbbab5 From c3132b1c0e697b7a9d7705101960353d27b0556d Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 27 May 2024 16:05:34 +0100 Subject: [PATCH 093/316] update natspec' --- src/FourSixTwoSixAgg.sol | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index eea68b59..a236c65a 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -9,12 +9,10 @@ import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumera import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {BalanceForwarder} from "./BalanceForwarder.sol"; -// @note Do NOT use with fee on transfer tokens -// @note Do NOT use with rebasing tokens -// @note Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol -// @note expired by Yearn v3 ❤️ -// TODO addons for reward stream support -// TODO custom withdraw queue support +/// @dev Do NOT use with fee on transfer tokens +/// @dev Do NOT use with rebasing tokens +/// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol +/// @dev inspired by Yearn v3 ❤️ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEnumerable { using SafeERC20 for IERC20; @@ -72,7 +70,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @dev A struct that hold a strategy allocation's config /// allocated: amount of asset deposited into strategy - /// allocationPoints number of points allocated to this strategy + /// allocationPoints: number of points allocated to this strategy /// active: a boolean to indice if this strategy is active or not struct Strategy { uint120 allocated; @@ -156,16 +154,14 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _disableBalanceForwarder(_msgSender()); } - /// @notice Rebalance strategy allocation. - /// @dev This function will first harvest yield, gulps and update interest. - /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. - /// If current allocation is less than target allocation, the aggregator will: - /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash - /// - If all the available cash is greater than the max deposit, deposit the max deposit - function rebalance(address strategy) external nonReentrant { - _rebalance(strategy); + /// @notice Rebalance strategy's allocation. + /// @param _strategy Address of strategy to rebalance. + function rebalance(address _strategy) external nonReentrant { + _rebalance(_strategy); } + /// @notice Rebalance multiple strategies. + /// @param _strategies An array of strategy addresses to rebalance. function rebalanceMultipleStrategies(address[] calldata _strategies) external nonReentrant { for (uint256 i; i < _strategies.length; ++i) { _rebalance(_strategies[i]); @@ -473,6 +469,13 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn esrSlot = esrSlotCache; } + /// @notice Rebalance strategy allocation. + /// @dev This function will first harvest yield, gulps and update interest. + /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. + /// If current allocation is less than target allocation, the aggregator will: + /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash + /// - If all the available cash is greater than the max deposit, deposit the max deposit + /// @param _strategy Address of strategy to rebalance. function _rebalance(address _strategy) internal { if (_strategy == address(0)) { return; //nothing to rebalance as this is the cash reserve From f4a9dd6472d30d4d8df853b93ec9aa5f9983bca8 Mon Sep 17 00:00:00 2001 From: totomanov Date: Tue, 28 May 2024 08:56:58 +0300 Subject: [PATCH 094/316] feat: simplify and optimize removeStrategy --- .gitignore | 5 ++- src/FourSixTwoSixAgg.sol | 18 +++++---- test/common/FourSixTwoSixAggBase.t.sol | 10 +++++ test/unit/RemoveStrategy.t.sol | 51 ++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 565f4937..d32ac12c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,7 @@ docs/ # coverage lcov.info -coverage \ No newline at end of file +coverage + +# gas +.gas-snapshot \ No newline at end of file diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index a236c65a..78969ee1 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -255,21 +255,23 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @dev Can only be called by an address that have the STRATEGY_REMOVER_ROLE /// @param strategy Address of the strategy function removeStrategy(address strategy) external nonReentrant onlyRole(STRATEGY_REMOVER_ROLE) { - if (!strategies[strategy].active) { + Strategy storage strategyStorage = strategies[strategy]; + + if (!strategyStorage.active) { revert AlreadyRemoved(); } - strategies[strategy].active = false; - totalAllocationPoints -= strategies[strategy].allocationPoints; - strategies[strategy].allocationPoints = 0; + totalAllocationPoints -= strategyStorage.allocationPoints; + strategyStorage.active = false; + strategyStorage.allocationPoints = 0; // remove from withdrawalQueue uint256 lastStrategyIndex = withdrawalQueue.length - 1; - for (uint256 i = 0; i <= lastStrategyIndex; i++) { - if ((withdrawalQueue[i] == strategy) && (i != lastStrategyIndex)) { - (withdrawalQueue[i], withdrawalQueue[lastStrategyIndex]) = - (withdrawalQueue[lastStrategyIndex], withdrawalQueue[i]); + for (uint256 i = 0; i < lastStrategyIndex; ++i) { + if (withdrawalQueue[i] == strategy) { + withdrawalQueue[i] = withdrawalQueue[lastStrategyIndex]; + withdrawalQueue[lastStrategyIndex] = strategy; } } diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 9730a538..c060fcfd 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -87,4 +87,14 @@ contract FourSixTwoSixAggBase is EVaultTestBase { vm.prank(from); fourSixTwoSixAgg.addStrategy(strategy, allocationPoints); } + + function _getWithdrawalQueue() internal view returns (address[] memory) { + uint256 length = fourSixTwoSixAgg.withdrawalQueueLength(); + + address[] memory queue = new address[](length); + for (uint256 i = 0; i < length; ++i) { + queue[i] = fourSixTwoSixAgg.withdrawalQueue(i); + } + return queue; + } } diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index e471c02d..9a2df1ce 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +import {console2} from "forge-std/console2.sol"; import {FourSixTwoSixAggBase, FourSixTwoSixAgg, IEVault} from "../common/FourSixTwoSixAggBase.t.sol"; contract RemoveStrategyTest is FourSixTwoSixAggBase { @@ -50,6 +51,56 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), withdrawalQueueLengthBefore - 1); } + function testRemoveStrategy_WithdrawalQueueOrdering() public { + address strategy1 = + factory.createProxy(address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount)); + address strategy2 = + factory.createProxy(address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount)); + address strategy3 = + factory.createProxy(address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount)); + + _addStrategy(manager, strategy1, strategyAllocationPoints); + _addStrategy(manager, strategy2, strategyAllocationPoints); + _addStrategy(manager, strategy3, strategyAllocationPoints); + + address[] memory withdrawalQueue = _getWithdrawalQueue(); + assertEq(withdrawalQueue.length, 4); + assertEq(withdrawalQueue[0], address(eTST)); + assertEq(withdrawalQueue[1], strategy1); + assertEq(withdrawalQueue[2], strategy2); + assertEq(withdrawalQueue[3], strategy3); + + vm.prank(manager); + fourSixTwoSixAgg.removeStrategy(strategy2); + + withdrawalQueue = _getWithdrawalQueue(); + assertEq(withdrawalQueue.length, 3); + assertEq(withdrawalQueue[0], address(eTST)); + assertEq(withdrawalQueue[1], strategy1); + assertEq(withdrawalQueue[2], strategy3); + + vm.prank(manager); + fourSixTwoSixAgg.removeStrategy(strategy3); + + withdrawalQueue = _getWithdrawalQueue(); + assertEq(withdrawalQueue.length, 2); + assertEq(withdrawalQueue[0], address(eTST)); + assertEq(withdrawalQueue[1], strategy1); + + vm.prank(manager); + fourSixTwoSixAgg.removeStrategy(address(eTST)); + + withdrawalQueue = _getWithdrawalQueue(); + assertEq(withdrawalQueue.length, 1); + assertEq(withdrawalQueue[0], strategy1); + + vm.prank(manager); + fourSixTwoSixAgg.removeStrategy(strategy1); + + withdrawalQueue = _getWithdrawalQueue(); + assertEq(withdrawalQueue.length, 0); + } + function testRemoveStrategy_fromUnauthorized() public { vm.prank(deployer); vm.expectRevert(); From cb734f656dc8dd1a710b1ded2409ce08c2acf66c Mon Sep 17 00:00:00 2001 From: totomanov Date: Tue, 28 May 2024 09:11:12 +0300 Subject: [PATCH 095/316] feat: various nits --- src/FourSixTwoSixAgg.sol | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 78969ee1..e5b258a2 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -120,14 +120,16 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn strategies[address(0)] = Strategy({allocated: 0, allocationPoints: uint120(_initialCashAllocationPoints), active: true}); - totalAllocationPoints += _initialCashAllocationPoints; + + uint256 _totalAllocationPoints = _initialCashAllocationPoints; for (uint256 i; i < _initialStrategies.length; ++i) { strategies[_initialStrategies[i]] = Strategy({allocated: 0, allocationPoints: uint120(_initialStrategiesAllocationPoints[i]), active: true}); - totalAllocationPoints += _initialStrategiesAllocationPoints[i]; + _totalAllocationPoints += _initialStrategiesAllocationPoints[i]; } + totalAllocationPoints = _totalAllocationPoints; // Setup DEFAULT_ADMIN _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); @@ -194,17 +196,13 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn onlyRole(ALLOCATION_ADJUSTER_ROLE) { Strategy memory strategyDataCache = strategies[strategy]; - uint256 totalAllocationPointsCache = totalAllocationPoints; if (!strategyDataCache.active) { revert InactiveStrategy(); } strategies[strategy].allocationPoints = uint120(newPoints); - - totalAllocationPoints = (newPoints > strategyDataCache.allocationPoints) - ? totalAllocationPointsCache + (newPoints - strategyDataCache.allocationPoints) - : totalAllocationPointsCache - (strategyDataCache.allocationPoints - newPoints); + totalAllocationPoints = totalAllocationPoints + newPoints - strategyDataCache.allocationPoints; } /// @notice Swap two strategies indexes in the withdrawal queue. @@ -216,7 +214,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn nonReentrant onlyRole(WITHDRAW_QUEUE_REORDERER_ROLE) { - if (index1 >= withdrawalQueue.length || index2 >= withdrawalQueue.length) { + uint256 length = withdrawalQueue.length; + if (index1 >= length || index2 >= length) { revert OutOfBounds(); } @@ -421,7 +420,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); - for (uint256 i; i < withdrawalQueue.length; i++) { + for (uint256 i; i < withdrawalQueue.length; ++i) { if (assetsRetrieved >= assets) { break; } From ce64f7308da20dab4be7f710ca72c8b186daf567 Mon Sep 17 00:00:00 2001 From: totomanov Date: Tue, 28 May 2024 09:35:22 +0300 Subject: [PATCH 096/316] feat: various --- src/FourSixTwoSixAgg.sol | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index e5b258a2..89d19dbb 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -18,8 +18,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn error Reentrancy(); error ArrayLengthMismatch(); - error AddressesOutOfOrder(); - error DuplicateInitialStrategy(); error InitialAllocationPointsZero(); error NotEnoughAssets(); error NegativeYield(); @@ -181,9 +179,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn function harvestMultipleStrategies(address[] calldata _strategies) external nonReentrant { for (uint256 i; i < _strategies.length; ++i) { _harvest(_strategies[i]); - - _gulp(); } + _gulp(); } /// @notice Adjust a certain strategy's allocation points. @@ -420,7 +417,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); - for (uint256 i; i < withdrawalQueue.length; ++i) { + uint256 numStrategies = withdrawalQueue.length; + for (uint256 i; i < numStrategies; ++i) { if (assetsRetrieved >= assets) { break; } @@ -430,16 +428,16 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _harvest(address(strategy)); _gulp(); - Strategy memory strategyData = strategies[withdrawalQueue[i]]; + Strategy storage strategyStorage = strategies[address(strategy)]; uint256 sharesBalance = strategy.balanceOf(address(this)); uint256 underlyingBalance = strategy.convertToAssets(sharesBalance); uint256 desiredAssets = assets - assetsRetrieved; - uint256 withdrawAmount = (underlyingBalance >= desiredAssets) ? desiredAssets : underlyingBalance; + uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; // Update allocated assets - strategies[withdrawalQueue[i]].allocated = strategyData.allocated - uint120(withdrawAmount); + strategyStorage.allocated -= uint120(withdrawAmount); totalAllocated -= withdrawAmount; // update assetsRetrieved From 2e650dbb588b361e58ecc4625c66dea0f1d1ec58 Mon Sep 17 00:00:00 2001 From: totomanov Date: Tue, 28 May 2024 10:00:38 +0300 Subject: [PATCH 097/316] rm: rogue import --- test/unit/RemoveStrategy.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index 9a2df1ce..9bfb2f66 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {console2} from "forge-std/console2.sol"; import {FourSixTwoSixAggBase, FourSixTwoSixAgg, IEVault} from "../common/FourSixTwoSixAggBase.t.sol"; contract RemoveStrategyTest is FourSixTwoSixAggBase { From 59ad9d43122f665992f2bd549f5a90f4a9c94175 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 28 May 2024 13:10:57 +0100 Subject: [PATCH 098/316] feat: performance fee impl --- src/FourSixTwoSixAgg.sol | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 89d19dbb..b6ff8cfc 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import {Context} from "@openzeppelin/utils/Context.sol"; import {ERC20, IERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; -import {ERC4626, IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; +import {ERC4626, IERC4626, Math} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumerable.sol"; import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {BalanceForwarder} from "./BalanceForwarder.sol"; @@ -42,6 +42,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn bytes32 public constant STRATEGY_REMOVER_ROLE = keccak256("STRATEGY_REMOVER_ROLE"); bytes32 public constant STRATEGY_REMOVER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMIN_ROLE"); + /// @dev The maximum fee the vault can have is 50% + uint256 internal constant MAX_FEE = 0.5e18; uint256 public constant INTEREST_SMEAR = 2 weeks; ESRSlot internal esrSlot; @@ -52,6 +54,10 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn uint256 public totalAllocated; /// @dev Total amount of allocation points across all strategies including the cash reserve. uint256 public totalAllocationPoints; + /// @dev fee rate + uint256 public fee; + /// @dev fee recipient address + address public feeRecipient; /// @dev An array of strategy addresses to withdraw from address[] public withdrawalQueue; @@ -139,6 +145,17 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMIN_ROLE); } + function setFee(uint256 _newFee) external { + if (_newFee > MAX_FEE) revert(); + if (_newFee != 0 && feeRecipient == address(0)) revert(); + if (_newFee == fee) revert(); + + // // Accrue fee using the previous fee set before changing it. + // _updateLastTotalAssets(_accrueFee()); + + fee = _newFee; + } + /// @notice Enables balance forwarding for sender /// @dev Should call the IBalanceTracker hook with the current user's balance function enableBalanceForwarder() external override nonReentrant { @@ -550,13 +567,24 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn uint256 yield = underlyingBalance - strategyData.allocated; strategies[strategy].allocated = uint120(underlyingBalance); totalAllocated += yield; - // TODO possible performance fee + + _accruePerformanceFee(yield); } else { // TODO handle losses revert NegativeYield(); } } + function _accruePerformanceFee(uint256 _yield) internal { + if (fee == 0) return; + + // `feeAssets` will be rounded down to 0 if `yield * fee < 1e18`. + uint256 feeAssets = _yield * fee / 1e18; + uint256 feeShares = _convertToShares(feeAssets, Math.Rounding.Down); + + if (feeShares != 0) _mint(feeRecipient, feeShares); + } + /// @dev Override _afterTokenTransfer hook to call IBalanceTracker.balanceTrackerHook() /// @dev Calling .balanceTrackerHook() passing the address total balance /// @param from Address sending the amount From bee036c56ca4cec222df044cc7e2eec0551a139f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 28 May 2024 19:10:46 +0100 Subject: [PATCH 099/316] add tests --- src/FourSixTwoSixAgg.sol | 56 ++++++--- test/common/FourSixTwoSixAggBase.t.sol | 8 ++ test/e2e/PerformanceFeeE2ETest.t.sol | 152 +++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 15 deletions(-) create mode 100644 test/e2e/PerformanceFeeE2ETest.t.sol diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index b6ff8cfc..99e08140 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -9,6 +9,8 @@ import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumera import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {BalanceForwarder} from "./BalanceForwarder.sol"; +import {Test, console2, stdError} from "forge-std/Test.sol"; + /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol @@ -27,6 +29,10 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn error InvalidStrategyAsset(); error StrategyAlreadyExist(); error AlreadyRemoved(); + error PerformanceFeeAlreadySet(); + error MaxPerformanceFeeExceeded(); + error FeeRecipientNotSet(); + error FeeRecipientAlreadySet(); uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; uint8 internal constant REENTRANCYLOCK__LOCKED = 2; @@ -41,9 +47,12 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn bytes32 public constant STRATEGY_ADDER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_ADDER_ROLE_ADMIN_ROLE"); bytes32 public constant STRATEGY_REMOVER_ROLE = keccak256("STRATEGY_REMOVER_ROLE"); bytes32 public constant STRATEGY_REMOVER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMIN_ROLE"); + bytes32 public constant PERFORMANCE_FEE_MANAGER_ROLE = keccak256("PERFORMANCE_FEE_MANAGER_ROLE"); + bytes32 public constant PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE = + keccak256("PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE"); - /// @dev The maximum fee the vault can have is 50% - uint256 internal constant MAX_FEE = 0.5e18; + /// @dev The maximum performanceFee the vault can have is 50% + uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; uint256 public constant INTEREST_SMEAR = 2 weeks; ESRSlot internal esrSlot; @@ -55,7 +64,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @dev Total amount of allocation points across all strategies including the cash reserve. uint256 public totalAllocationPoints; /// @dev fee rate - uint256 public fee; + uint256 public performanceFee; /// @dev fee recipient address address public feeRecipient; @@ -143,17 +152,21 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _setRoleAdmin(WITHDRAW_QUEUE_REORDERER_ROLE, WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE); _setRoleAdmin(STRATEGY_ADDER_ROLE, STRATEGY_ADDER_ROLE_ADMIN_ROLE); _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMIN_ROLE); + _setRoleAdmin(PERFORMANCE_FEE_MANAGER_ROLE, PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE); } - function setFee(uint256 _newFee) external { - if (_newFee > MAX_FEE) revert(); - if (_newFee != 0 && feeRecipient == address(0)) revert(); - if (_newFee == fee) revert(); + function setFeeRecipient(address newFeeRecipient) external onlyRole(PERFORMANCE_FEE_MANAGER_ROLE) { + if (newFeeRecipient == feeRecipient) revert FeeRecipientAlreadySet(); + + feeRecipient = newFeeRecipient; + } - // // Accrue fee using the previous fee set before changing it. - // _updateLastTotalAssets(_accrueFee()); + function setPerformanceFee(uint256 _newFee) external onlyRole(PERFORMANCE_FEE_MANAGER_ROLE) { + if (_newFee > MAX_PERFORMANCE_FEE) revert MaxPerformanceFeeExceeded(); + if (feeRecipient == address(0)) revert FeeRecipientNotSet(); + if (_newFee == performanceFee) revert PerformanceFeeAlreadySet(); - fee = _newFee; + performanceFee = _newFee; } /// @notice Enables balance forwarding for sender @@ -412,6 +425,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @dev the total assets allocatable is the amount of assets deposited into the aggregator + assets already deposited into strategies /// @return uint256 total assets function totalAssetsAllocatable() public view returns (uint256) { + console2.log("this balanceOf", IERC20(asset()).balanceOf(address(this))); + console2.log("totalAllocated", totalAllocated); return IERC20(asset()).balanceOf(address(this)) + totalAllocated; } @@ -434,6 +449,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); + console2.log("assetsRetrieved", assetsRetrieved); uint256 numStrategies = withdrawalQueue.length; for (uint256 i; i < numStrategies; ++i) { if (assetsRetrieved >= assets) { @@ -443,7 +459,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn IERC4626 strategy = IERC4626(withdrawalQueue[i]); _harvest(address(strategy)); - _gulp(); Strategy storage strategyStorage = strategies[address(strategy)]; @@ -463,16 +478,25 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn // Do actual withdraw from strategy strategy.withdraw(withdrawAmount, address(this), address(this)); } + console2.log("assetsRetrieved done", assetsRetrieved); + + _gulp(); + console2.log("here"); if (assetsRetrieved < assets) { revert NotEnoughAssets(); } - + console2.log("shares", shares); super._withdraw(caller, receiver, owner, assets, shares); } function _gulp() internal { ESRSlot memory esrSlotCache = updateInterestAndReturnESRSlotCache(); + console2.log("totalAssetsAllocatable()", totalAssetsAllocatable()); + console2.log("totalAssetsDeposited", totalAssetsDeposited); + console2.log("esrSlotCache.interestLeft", esrSlotCache.interestLeft); + + if (totalAssetsDeposited == 0) return; uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft; uint256 maxGulp = type(uint168).max - esrSlotCache.interestLeft; @@ -568,6 +592,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn strategies[strategy].allocated = uint120(underlyingBalance); totalAllocated += yield; + console2.log("yield", yield); + _accruePerformanceFee(yield); } else { // TODO handle losses @@ -576,10 +602,10 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn } function _accruePerformanceFee(uint256 _yield) internal { - if (fee == 0) return; + if (performanceFee == 0) return; - // `feeAssets` will be rounded down to 0 if `yield * fee < 1e18`. - uint256 feeAssets = _yield * fee / 1e18; + // `feeAssets` will be rounded down to 0 if `yield * performanceFee < 1e18`. + uint256 feeAssets = Math.mulDiv(_yield, performanceFee, 1e18, Math.Rounding.Down); uint256 feeShares = _convertToShares(feeAssets, Math.Rounding.Down); if (feeShares != 0) _mint(feeRecipient, feeShares); diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index c060fcfd..54845492 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -38,12 +38,14 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE(), deployer); // grant roles to manager fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE(), manager); vm.stopPrank(); } @@ -71,16 +73,22 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE()), fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE() ); + assertEq( + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE()), + fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE() + ); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE(), manager)); } function _addStrategy(address from, address strategy, uint256 allocationPoints) internal { diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol new file mode 100644 index 00000000..8fd09e9e --- /dev/null +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + FourSixTwoSixAggBase, + FourSixTwoSixAgg, + console2, + EVault, + IEVault, + IRMTestDefault, + TestERC20 +} from "../common/FourSixTwoSixAggBase.t.sol"; + +contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { + uint256 user1InitialBalance = 100000e18; + + address feeRecipient; + + function setUp() public virtual override { + super.setUp(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + + assetTST.mint(user1, user1InitialBalance); + + feeRecipient = makeAddr("FEE_RECIPIENT"); + } + + function testSetPerformanceFee() public { + assertEq(fourSixTwoSixAgg.performanceFee(), 0); + + uint256 newPerformanceFee = 3e17; + + vm.startPrank(manager); + fourSixTwoSixAgg.setFeeRecipient(feeRecipient); + fourSixTwoSixAgg.setPerformanceFee(newPerformanceFee); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.performanceFee(), newPerformanceFee); + assertEq(fourSixTwoSixAgg.feeRecipient(), feeRecipient); + } + + function testHarvestWithFeeEnabled() public { + uint256 newPerformanceFee = 3e17; + + vm.startPrank(manager); + fourSixTwoSixAgg.setFeeRecipient(feeRecipient); + fourSixTwoSixAgg.setPerformanceFee(newPerformanceFee); + vm.stopPrank(); + + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + } + + vm.warp(block.timestamp + 86400); + // mock an increase of strategy balance by 10% + uint256 yield; + { + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); + uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; + yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; + assetTST.mint(address(eTST), yield); + eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + } + + uint256 expectedPerformanceFee = yield * fourSixTwoSixAgg.performanceFee() / 1e18; + uint256 expectedPerformanceFeeShares = fourSixTwoSixAgg.convertToShares(expectedPerformanceFee); + + // harvest + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + + assertEq(fourSixTwoSixAgg.balanceOf(feeRecipient), expectedPerformanceFeeShares); + + // full withdraw, will have to withdraw from strategy as cash reserve is not enough + { + uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + uint256 expectedAssetTST = fourSixTwoSixAgg.convertToAssets(fourSixTwoSixAgg.balanceOf(user1)); + + vm.prank(user1); + fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + + // all yield is distributed + // assertApproxEqAbs(eTST.balanceOf(address(fourSixTwoSixAgg)), expectePerformanceFeeAssets, 1); + assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssetTST, 1); + assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedAssetTST, 1); + } + + // full withdraw of recipient fees + { + // uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + // uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 assetTSTBalanceBefore = assetTST.balanceOf(feeRecipient); + + uint256 feeShares = fourSixTwoSixAgg.balanceOf(feeRecipient); + uint256 expectedAssets = fourSixTwoSixAgg.convertToAssets(feeShares); + console2.log("feeShares", feeShares); + console2.log("expectedAssets", expectedAssets); + console2.log("totalSuppy", fourSixTwoSixAgg.totalSupply()); + + vm.prank(feeRecipient); + fourSixTwoSixAgg.redeem(feeShares, feeRecipient, feeRecipient); + + // all yield is distributed + // assertApproxEqAbs(eTST.balanceOf(address(fourSixTwoSixAgg)), 0, 1); + assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssets, 1); + assertEq(fourSixTwoSixAgg.totalSupply(), 0); + assertApproxEqAbs(assetTST.balanceOf(feeRecipient), assetTSTBalanceBefore + expectedAssets, 1); + } + } +} From f8c05a5b3fc29834ea3436314588027e96e1c5b0 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 28 May 2024 19:37:10 +0100 Subject: [PATCH 100/316] fix --- test/e2e/BalanceForwarderE2ETest.t.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index d1597e95..87bbaac8 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -39,11 +39,14 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE(), deployer); + // grant roles to manager fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; From 801fd3dcf41f0b14799deeb2734b6592518e97c6 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 28 May 2024 20:05:33 +0100 Subject: [PATCH 101/316] clean --- test/e2e/PerformanceFeeE2ETest.t.sol | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 8fd09e9e..a7ce59af 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -128,17 +128,11 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { // full withdraw of recipient fees { - // uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); - // uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); uint256 assetTSTBalanceBefore = assetTST.balanceOf(feeRecipient); uint256 feeShares = fourSixTwoSixAgg.balanceOf(feeRecipient); uint256 expectedAssets = fourSixTwoSixAgg.convertToAssets(feeShares); - console2.log("feeShares", feeShares); - console2.log("expectedAssets", expectedAssets); - console2.log("totalSuppy", fourSixTwoSixAgg.totalSupply()); - vm.prank(feeRecipient); fourSixTwoSixAgg.redeem(feeShares, feeRecipient, feeRecipient); From dd28b150b331de902caa231306c6879f9c1b7250 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 29 May 2024 10:01:07 +0100 Subject: [PATCH 102/316] clean --- src/FourSixTwoSixAgg.sol | 14 +------------- test/e2e/PerformanceFeeE2ETest.t.sol | 4 ---- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 99e08140..5a0f4ccb 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -9,8 +9,6 @@ import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumera import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {BalanceForwarder} from "./BalanceForwarder.sol"; -import {Test, console2, stdError} from "forge-std/Test.sol"; - /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol @@ -425,8 +423,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @dev the total assets allocatable is the amount of assets deposited into the aggregator + assets already deposited into strategies /// @return uint256 total assets function totalAssetsAllocatable() public view returns (uint256) { - console2.log("this balanceOf", IERC20(asset()).balanceOf(address(this))); - console2.log("totalAllocated", totalAllocated); return IERC20(asset()).balanceOf(address(this)) + totalAllocated; } @@ -449,7 +445,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); - console2.log("assetsRetrieved", assetsRetrieved); uint256 numStrategies = withdrawalQueue.length; for (uint256 i; i < numStrategies; ++i) { if (assetsRetrieved >= assets) { @@ -478,23 +473,18 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn // Do actual withdraw from strategy strategy.withdraw(withdrawAmount, address(this), address(this)); } - console2.log("assetsRetrieved done", assetsRetrieved); _gulp(); - console2.log("here"); if (assetsRetrieved < assets) { revert NotEnoughAssets(); } - console2.log("shares", shares); + super._withdraw(caller, receiver, owner, assets, shares); } function _gulp() internal { ESRSlot memory esrSlotCache = updateInterestAndReturnESRSlotCache(); - console2.log("totalAssetsAllocatable()", totalAssetsAllocatable()); - console2.log("totalAssetsDeposited", totalAssetsDeposited); - console2.log("esrSlotCache.interestLeft", esrSlotCache.interestLeft); if (totalAssetsDeposited == 0) return; uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft; @@ -592,8 +582,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn strategies[strategy].allocated = uint120(underlyingBalance); totalAllocated += yield; - console2.log("yield", yield); - _accruePerformanceFee(yield); } else { // TODO handle losses diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index a7ce59af..238dbb93 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -119,8 +119,6 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { vm.prank(user1); fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); - // all yield is distributed - // assertApproxEqAbs(eTST.balanceOf(address(fourSixTwoSixAgg)), expectePerformanceFeeAssets, 1); assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssetTST, 1); assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedAssetTST, 1); @@ -136,8 +134,6 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { vm.prank(feeRecipient); fourSixTwoSixAgg.redeem(feeShares, feeRecipient, feeRecipient); - // all yield is distributed - // assertApproxEqAbs(eTST.balanceOf(address(fourSixTwoSixAgg)), 0, 1); assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssets, 1); assertEq(fourSixTwoSixAgg.totalSupply(), 0); assertApproxEqAbs(assetTST.balanceOf(feeRecipient), assetTSTBalanceBefore + expectedAssets, 1); From 09ea8245577c359136e2d1ed650774a3a7e1274b Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 29 May 2024 10:10:59 +0100 Subject: [PATCH 103/316] chore: add natspec --- src/FourSixTwoSixAgg.sol | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 5a0f4ccb..6e38f2d6 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -153,12 +153,16 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _setRoleAdmin(PERFORMANCE_FEE_MANAGER_ROLE, PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE); } - function setFeeRecipient(address newFeeRecipient) external onlyRole(PERFORMANCE_FEE_MANAGER_ROLE) { - if (newFeeRecipient == feeRecipient) revert FeeRecipientAlreadySet(); + /// @notice Set performance fee recipient address + /// @notice @param _newFeeRecipient Recipient address + function setFeeRecipient(address _newFeeRecipient) external onlyRole(PERFORMANCE_FEE_MANAGER_ROLE) { + if (_newFeeRecipient == feeRecipient) revert FeeRecipientAlreadySet(); - feeRecipient = newFeeRecipient; + feeRecipient = _newFeeRecipient; } + /// @notice Set performance fee (1e18 == 100%) + /// @notice @param _newFee Fee rate function setPerformanceFee(uint256 _newFee) external onlyRole(PERFORMANCE_FEE_MANAGER_ROLE) { if (_newFee > MAX_PERFORMANCE_FEE) revert MaxPerformanceFeeExceeded(); if (feeRecipient == address(0)) revert FeeRecipientNotSet(); From eac47248a2e9839757318ea4d011ae4fc366656e Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 29 May 2024 12:25:29 +0100 Subject: [PATCH 104/316] feat: opt in and out of strategy rewards --- src/BalanceForwarder.sol | 2 +- src/FourSixTwoSixAgg.sol | 26 +++++++++++++++++++------- test/common/FourSixTwoSixAggBase.t.sol | 12 ++++++------ test/e2e/BalanceForwarderE2ETest.t.sol | 4 ++-- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/BalanceForwarder.sol b/src/BalanceForwarder.sol index e0e67622..f6c66dbf 100644 --- a/src/BalanceForwarder.sol +++ b/src/BalanceForwarder.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import {IBalanceForwarder} from "./interface/IBalanceForwarder.sol"; import {IBalanceTracker} from "./interface/IBalanceTracker.sol"; -/// @title BalanceForwarderModule +/// @title BalanceForwarder contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) /// @notice A generic contract to integrate with https://github.com/euler-xyz/reward-streams diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 6e38f2d6..acf67f16 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -7,7 +7,7 @@ import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; import {ERC4626, IERC4626, Math} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumerable.sol"; import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; -import {BalanceForwarder} from "./BalanceForwarder.sol"; +import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens @@ -45,9 +45,9 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn bytes32 public constant STRATEGY_ADDER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_ADDER_ROLE_ADMIN_ROLE"); bytes32 public constant STRATEGY_REMOVER_ROLE = keccak256("STRATEGY_REMOVER_ROLE"); bytes32 public constant STRATEGY_REMOVER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMIN_ROLE"); - bytes32 public constant PERFORMANCE_FEE_MANAGER_ROLE = keccak256("PERFORMANCE_FEE_MANAGER_ROLE"); - bytes32 public constant PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE = - keccak256("PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE"); + bytes32 public constant TREASURY_MANAGER_ROLE = keccak256("TREASURY_MANAGER_ROLE"); + bytes32 public constant TREASURY_MANAGER_ROLE_ADMIN_ROLE = + keccak256("TREASURY_MANAGER_ROLE_ADMIN_ROLE"); /// @dev The maximum performanceFee the vault can have is 50% uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; @@ -150,12 +150,12 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _setRoleAdmin(WITHDRAW_QUEUE_REORDERER_ROLE, WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE); _setRoleAdmin(STRATEGY_ADDER_ROLE, STRATEGY_ADDER_ROLE_ADMIN_ROLE); _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMIN_ROLE); - _setRoleAdmin(PERFORMANCE_FEE_MANAGER_ROLE, PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE); + _setRoleAdmin(TREASURY_MANAGER_ROLE, TREASURY_MANAGER_ROLE_ADMIN_ROLE); } /// @notice Set performance fee recipient address /// @notice @param _newFeeRecipient Recipient address - function setFeeRecipient(address _newFeeRecipient) external onlyRole(PERFORMANCE_FEE_MANAGER_ROLE) { + function setFeeRecipient(address _newFeeRecipient) external onlyRole(TREASURY_MANAGER_ROLE) { if (_newFeeRecipient == feeRecipient) revert FeeRecipientAlreadySet(); feeRecipient = _newFeeRecipient; @@ -163,7 +163,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @notice Set performance fee (1e18 == 100%) /// @notice @param _newFee Fee rate - function setPerformanceFee(uint256 _newFee) external onlyRole(PERFORMANCE_FEE_MANAGER_ROLE) { + function setPerformanceFee(uint256 _newFee) external onlyRole(TREASURY_MANAGER_ROLE) { if (_newFee > MAX_PERFORMANCE_FEE) revert MaxPerformanceFeeExceeded(); if (feeRecipient == address(0)) revert FeeRecipientNotSet(); if (_newFee == performanceFee) revert PerformanceFeeAlreadySet(); @@ -171,6 +171,18 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn performanceFee = _newFee; } + function optInStrategyRewards(address _strategy) external onlyRole(TREASURY_MANAGER_ROLE) { + if(!strategies[_strategy].active) revert InactiveStrategy(); + + IBalanceForwarder(_strategy).enableBalanceForwarder(); + } + + function optOutStrategyRewards(address _strategy) external onlyRole(TREASURY_MANAGER_ROLE) { + if(!strategies[_strategy].active) revert InactiveStrategy(); + + IBalanceForwarder(_strategy).disableBalanceForwarder(); + } + /// @notice Enables balance forwarding for sender /// @dev Should call the IBalanceTracker hook with the current user's balance function enableBalanceForwarder() external override nonReentrant { diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 54845492..389a2528 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -38,14 +38,14 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMIN_ROLE(), deployer); // grant roles to manager fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE(), manager); vm.stopPrank(); } @@ -74,21 +74,21 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE() ); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE()), - fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE()), + fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMIN_ROLE() ); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMIN_ROLE(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE(), manager)); } function _addStrategy(address from, address strategy, uint256 allocationPoints) internal { diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 87bbaac8..746ae0b3 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -39,14 +39,14 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMIN_ROLE(), deployer); // grant roles to manager fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.PERFORMANCE_FEE_MANAGER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; From b34d179bf466c1fa51659feb5d3ca38799e893e0 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 29 May 2024 12:28:43 +0100 Subject: [PATCH 105/316] fix --- src/FourSixTwoSixAgg.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 6e38f2d6..80c32702 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -594,7 +594,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn } function _accruePerformanceFee(uint256 _yield) internal { - if (performanceFee == 0) return; + if (feeRecipient == address(0) || performanceFee == 0) return; // `feeAssets` will be rounded down to 0 if `yield * performanceFee < 1e18`. uint256 feeAssets = Math.mulDiv(_yield, performanceFee, 1e18, Math.Rounding.Down); From 079fc002a576c06a5202c73b607c2358ca4ac69e Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 29 May 2024 12:29:35 +0100 Subject: [PATCH 106/316] chore: lint --- src/FourSixTwoSixAgg.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index acf67f16..06b8b532 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -46,8 +46,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn bytes32 public constant STRATEGY_REMOVER_ROLE = keccak256("STRATEGY_REMOVER_ROLE"); bytes32 public constant STRATEGY_REMOVER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMIN_ROLE"); bytes32 public constant TREASURY_MANAGER_ROLE = keccak256("TREASURY_MANAGER_ROLE"); - bytes32 public constant TREASURY_MANAGER_ROLE_ADMIN_ROLE = - keccak256("TREASURY_MANAGER_ROLE_ADMIN_ROLE"); + bytes32 public constant TREASURY_MANAGER_ROLE_ADMIN_ROLE = keccak256("TREASURY_MANAGER_ROLE_ADMIN_ROLE"); /// @dev The maximum performanceFee the vault can have is 50% uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; @@ -172,13 +171,13 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn } function optInStrategyRewards(address _strategy) external onlyRole(TREASURY_MANAGER_ROLE) { - if(!strategies[_strategy].active) revert InactiveStrategy(); + if (!strategies[_strategy].active) revert InactiveStrategy(); IBalanceForwarder(_strategy).enableBalanceForwarder(); } function optOutStrategyRewards(address _strategy) external onlyRole(TREASURY_MANAGER_ROLE) { - if(!strategies[_strategy].active) revert InactiveStrategy(); + if (!strategies[_strategy].active) revert InactiveStrategy(); IBalanceForwarder(_strategy).disableBalanceForwarder(); } From 8656756554b37b5ee4c428eaee0e0ccc391a13f3 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 29 May 2024 13:52:50 +0100 Subject: [PATCH 107/316] feat: claimStrategyReward() --- src/BalanceForwarder.sol | 2 +- src/FourSixTwoSixAgg.sol | 13 +++++++++++++ src/interface/IBalanceTracker.sol | 19 ------------------- 3 files changed, 14 insertions(+), 20 deletions(-) delete mode 100644 src/interface/IBalanceTracker.sol diff --git a/src/BalanceForwarder.sol b/src/BalanceForwarder.sol index f6c66dbf..cd8e4aaf 100644 --- a/src/BalanceForwarder.sol +++ b/src/BalanceForwarder.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {IBalanceForwarder} from "./interface/IBalanceForwarder.sol"; -import {IBalanceTracker} from "./interface/IBalanceTracker.sol"; +import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; /// @title BalanceForwarder contract /// @custom:security-contact security@euler.xyz diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 06b8b532..beede08c 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -8,6 +8,7 @@ import {ERC4626, IERC4626, Math} from "@openzeppelin/token/ERC20/extensions/ERC4 import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumerable.sol"; import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; +import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens @@ -182,6 +183,18 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn IBalanceForwarder(_strategy).disableBalanceForwarder(); } + function claimStrategyReward( + address _strategy, + address _rewarded, + address _reward, + address _recipient, + bool _forfeitRecentReward + ) external onlyRole(TREASURY_MANAGER_ROLE) { + address rewardStreams = IBalanceForwarder(_strategy).balanceTrackerAddress(); + + IRewardStreams(rewardStreams).claimReward(_rewarded, _reward, _recipient, _forfeitRecentReward); + } + /// @notice Enables balance forwarding for sender /// @dev Should call the IBalanceTracker hook with the current user's balance function enableBalanceForwarder() external override nonReentrant { diff --git a/src/interface/IBalanceTracker.sol b/src/interface/IBalanceTracker.sol deleted file mode 100644 index 82a47d7b..00000000 --- a/src/interface/IBalanceTracker.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity ^0.8.0; - -/// @title IBalanceTracker -/// @author Euler Labs (https://www.eulerlabs.com/) -/// @notice Provides an interface for tracking the balance of accounts. -interface IBalanceTracker { - /// @notice Executes the balance tracking hook for an account. - /// @dev This function is called by the Balance Forwarder contract which was enabled for the account. This function - /// must be called with the current balance of the account when enabling the balance forwarding for it. This - /// function must be called with 0 balance of the account when disabling the balance forwarding for it. This - /// function allows to be called on zero balance transfers, when the newAccountBalance is the same as the previous - /// one. To prevent DOS attacks, forfeitRecentReward should be used appropriately. - /// @param account The account address to execute the hook for. - /// @param newAccountBalance The new balance of the account. - /// @param forfeitRecentReward Whether to forfeit the most recent reward and not update the accumulator. - function balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external; -} From 266e804feebf4596ab76b04b73360729078b7d16 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 30 May 2024 11:01:06 +0100 Subject: [PATCH 108/316] chore: add natspec --- src/FourSixTwoSixAgg.sol | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 59bffad1..5f79f742 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -171,18 +171,26 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn performanceFee = _newFee; } + /// @notice Opt in to strategy rewards + /// @param _strategy Strategy address function optInStrategyRewards(address _strategy) external onlyRole(TREASURY_MANAGER_ROLE) { if (!strategies[_strategy].active) revert InactiveStrategy(); IBalanceForwarder(_strategy).enableBalanceForwarder(); } + /// @notice Opt out of strategy rewards + /// @param _strategy Strategy address function optOutStrategyRewards(address _strategy) external onlyRole(TREASURY_MANAGER_ROLE) { - if (!strategies[_strategy].active) revert InactiveStrategy(); - IBalanceForwarder(_strategy).disableBalanceForwarder(); } + /// @notice Claim a specific strategy rewards + /// @param _strategy Strategy address. + /// @param _rewarded The address of the rewarded token. + /// @param _reward The address of the reward token. + /// @param _recipient The address to receive the claimed reward tokens. + /// @param _forfeitRecentReward Whether to forfeit the recent rewards and not update the accumulator. function claimStrategyReward( address _strategy, address _rewarded, From 700111d543e8fd7804b67ce63a4762cca84b0640 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 30 May 2024 11:01:21 +0100 Subject: [PATCH 109/316] test: --- test/e2e/StrategyRewardsE2ETest.t.sol | 44 +++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test/e2e/StrategyRewardsE2ETest.t.sol diff --git a/test/e2e/StrategyRewardsE2ETest.t.sol b/test/e2e/StrategyRewardsE2ETest.t.sol new file mode 100644 index 00000000..736a678d --- /dev/null +++ b/test/e2e/StrategyRewardsE2ETest.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + FourSixTwoSixAggBase, + FourSixTwoSixAgg, + console2, + EVault, + IEVault, + IRMTestDefault, + TestERC20 +} from "../common/FourSixTwoSixAggBase.t.sol"; +import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol"; + +contract StrategyRewardsE2ETest is FourSixTwoSixAggBase { + uint256 user1InitialBalance = 100000e18; + + function setUp() public virtual override { + super.setUp(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + + assetTST.mint(user1, user1InitialBalance); + } + + function testOptInStrategyRewards() public { + vm.prank(manager); + fourSixTwoSixAgg.optInStrategyRewards(address(eTST)); + + assertTrue(eTST.balanceForwarderEnabled(address(fourSixTwoSixAgg))); + } + + function testOptOutStrategyRewards() public { + vm.prank(manager); + fourSixTwoSixAgg.optInStrategyRewards(address(eTST)); + assertTrue(eTST.balanceForwarderEnabled(address(fourSixTwoSixAgg))); + + vm.prank(manager); + fourSixTwoSixAgg.optOutStrategyRewards(address(eTST)); + + assertFalse(eTST.balanceForwarderEnabled(address(fourSixTwoSixAgg))); + } +} From fdfdc1775bd336ca70bcd02521259bf56865d980 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 30 May 2024 11:01:36 +0100 Subject: [PATCH 110/316] chore: lint --- test/e2e/StrategyRewardsE2ETest.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/StrategyRewardsE2ETest.t.sol b/test/e2e/StrategyRewardsE2ETest.t.sol index 736a678d..edbcb9a9 100644 --- a/test/e2e/StrategyRewardsE2ETest.t.sol +++ b/test/e2e/StrategyRewardsE2ETest.t.sol @@ -27,7 +27,7 @@ contract StrategyRewardsE2ETest is FourSixTwoSixAggBase { function testOptInStrategyRewards() public { vm.prank(manager); fourSixTwoSixAgg.optInStrategyRewards(address(eTST)); - + assertTrue(eTST.balanceForwarderEnabled(address(fourSixTwoSixAgg))); } @@ -38,7 +38,7 @@ contract StrategyRewardsE2ETest is FourSixTwoSixAggBase { vm.prank(manager); fourSixTwoSixAgg.optOutStrategyRewards(address(eTST)); - + assertFalse(eTST.balanceForwarderEnabled(address(fourSixTwoSixAgg))); } } From 10a6db8d23ecb0a6d10f86413b19efeb0060f10c Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 30 May 2024 13:42:59 +0100 Subject: [PATCH 111/316] feat: socialize negative yield --- src/FourSixTwoSixAgg.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 5f79f742..607dbbc1 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -620,8 +620,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _accruePerformanceFee(yield); } else { - // TODO handle losses - revert NegativeYield(); + uint256 socializedLoss = strategyData.allocated - underlyingBalance; + totalAssetsDeposited -= socializedLoss; } } From 61a19e3acc110760d6b9a2617b8ba470c7f67164 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 31 May 2024 16:52:35 +0100 Subject: [PATCH 112/316] fix and tests' --- src/FourSixTwoSixAgg.sol | 7 +-- test/unit/HarvestTest.t.sol | 85 ++++++++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 607dbbc1..38d758b4 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -10,6 +10,8 @@ import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; +import {Test, console2, stdError} from "forge-std/Test.sol"; + /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol @@ -437,7 +439,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn function updateInterestAndReturnESRSlotCache() public returns (ESRSlot memory) { ESRSlot memory esrSlotCache = esrSlot; uint256 accruedInterest = interestAccruedFromCache(esrSlotCache); - // it's safe to down-cast because the accrued interest is a fraction of interest left esrSlotCache.interestLeft -= uint168(accruedInterest); esrSlotCache.lastInterestUpdate = uint40(block.timestamp); @@ -524,7 +525,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn if (totalAssetsDeposited == 0) return; uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft; - uint256 maxGulp = type(uint168).max - esrSlotCache.interestLeft; if (toGulp > maxGulp) toGulp = maxGulp; // cap interest, allowing the vault to function @@ -606,7 +606,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn Strategy memory strategyData = strategies[strategy]; if (strategyData.allocated == 0) return; - uint256 sharesBalance = IERC4626(strategy).balanceOf(address(this)); uint256 underlyingBalance = IERC4626(strategy).convertToAssets(sharesBalance); @@ -621,6 +620,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _accruePerformanceFee(yield); } else { uint256 socializedLoss = strategyData.allocated - underlyingBalance; + strategies[strategy].allocated = uint120(underlyingBalance); + // totalAllocated -= socializedLoss; totalAssetsDeposited -= socializedLoss; } } diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index 0f68a5ae..e5039756 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -95,7 +95,7 @@ contract HarvestTest is FourSixTwoSixAggBase { function testHarvestNegativeYield() public { vm.warp(block.timestamp + 86400); - // mock an increase of strategy balance by 10% + // mock a decrease of strategy balance by 10% uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); vm.mockCall( address(eTST), @@ -107,8 +107,89 @@ contract HarvestTest is FourSixTwoSixAggBase { assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated); + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + } + + function testHarvestNegativeYieldAndWithdrawSingleUser() public { + vm.warp(block.timestamp + 86400); + + // mock a decrease of strategy balance by 10% + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encode(aggrCurrentStrategyBalanceAfterNegYield) + ); + + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated); + uint256 negativeYield = + strategyBefore.allocated - eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))); + + uint256 user1SharesBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / fourSixTwoSixAgg.totalSupply(); + uint256 expectedUser1Assets = + user1SharesBefore * amountToDeposit / fourSixTwoSixAgg.totalSupply() - user1SocializedLoss; + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + vm.startPrank(user1); - vm.expectRevert(FourSixTwoSixAgg.NegativeYield.selector); fourSixTwoSixAgg.harvest(address(eTST)); + fourSixTwoSixAgg.redeem(user1SharesBefore, user1, user1); + vm.stopPrank(); + + uint256 user1SharesAfter = fourSixTwoSixAgg.balanceOf(user1); + + assertEq(user1SharesAfter, 0); + assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedUser1Assets, 1); + } + + function testHarvestNegativeYieldwMultipleUser() public { + uint256 user2InitialBalance = 5000e18; + assetTST.mint(user2, user2InitialBalance); + // deposit into aggregator + { + vm.startPrank(user2); + assetTST.approve(address(fourSixTwoSixAgg), user2InitialBalance); + fourSixTwoSixAgg.deposit(user2InitialBalance, user2); + vm.stopPrank(); + } + + vm.warp(block.timestamp + 86400); + + // mock a decrease of strategy balance by 10% + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encode(aggrCurrentStrategyBalanceAfterNegYield) + ); + + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated); + uint256 negativeYield = + strategyBefore.allocated - eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))); + uint256 user1SharesBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / fourSixTwoSixAgg.totalSupply(); + uint256 user2SharesBefore = fourSixTwoSixAgg.balanceOf(user2); + uint256 user2SocializedLoss = user2SharesBefore * negativeYield / fourSixTwoSixAgg.totalSupply(); + + uint256 expectedUser1Assets = user1SharesBefore * (amountToDeposit + user2InitialBalance) + / fourSixTwoSixAgg.totalSupply() - user1SocializedLoss; + uint256 expectedUser2Assets = user2SharesBefore * (amountToDeposit + user2InitialBalance) + / fourSixTwoSixAgg.totalSupply() - user2SocializedLoss; + + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + + uint256 user1SharesAfter = fourSixTwoSixAgg.balanceOf(user1); + uint256 user1AssetsAfter = fourSixTwoSixAgg.convertToAssets(user1SharesAfter); + uint256 user2SharesAfter = fourSixTwoSixAgg.balanceOf(user2); + uint256 user2AssetsAfter = fourSixTwoSixAgg.convertToAssets(user2SharesAfter); + + assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets, 1); + assertApproxEqAbs(user2AssetsAfter, expectedUser2Assets, 1); } } From 5c4746b1492bfefc1ba9f41fdfa4cd37d2ed84a6 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 31 May 2024 16:53:10 +0100 Subject: [PATCH 113/316] decrease totalAllocated --- src/FourSixTwoSixAgg.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 38d758b4..cc63807c 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -621,7 +621,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn } else { uint256 socializedLoss = strategyData.allocated - underlyingBalance; strategies[strategy].allocated = uint120(underlyingBalance); - // totalAllocated -= socializedLoss; + totalAllocated -= socializedLoss; totalAssetsDeposited -= socializedLoss; } } From 034c2e514dbb388f5a6f738b54cf98a367315099 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 3 Jun 2024 14:45:09 +0100 Subject: [PATCH 114/316] fix --- src/FourSixTwoSixAgg.sol | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index cc63807c..57804dc0 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -525,6 +525,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn if (totalAssetsDeposited == 0) return; uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft; + /// TODO return if toGulp == 0 uint256 maxGulp = type(uint168).max - esrSlotCache.interestLeft; if (toGulp > maxGulp) toGulp = maxGulp; // cap interest, allowing the vault to function @@ -619,10 +620,19 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _accruePerformanceFee(yield); } else { - uint256 socializedLoss = strategyData.allocated - underlyingBalance; + uint256 loss = strategyData.allocated - underlyingBalance; + strategies[strategy].allocated = uint120(underlyingBalance); - totalAllocated -= socializedLoss; - totalAssetsDeposited -= socializedLoss; + totalAllocated -= loss; + + ESRSlot memory esrSlotCache = esrSlot; + if (esrSlotCache.interestLeft >= loss) { + esrSlotCache.interestLeft -= uint168(loss); + } else { + totalAssetsDeposited -= loss - esrSlotCache.interestLeft; + esrSlotCache.interestLeft = 0; + } + esrSlot = esrSlotCache; } } From 0262537185ba9cc113a75ff945c81d5c84d1cb92 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 3 Jun 2024 14:45:19 +0100 Subject: [PATCH 115/316] more gulping tests --- test/unit/GulpTest.t.sol | 157 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 test/unit/GulpTest.t.sol diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol new file mode 100644 index 00000000..c96cb3d1 --- /dev/null +++ b/test/unit/GulpTest.t.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault} from "../common/FourSixTwoSixAggBase.t.sol"; + +contract GulpTest is FourSixTwoSixAggBase { + uint256 user1InitialBalance = 100000e18; + uint256 amountToDeposit = 10000e18; + + function setUp() public virtual override { + super.setUp(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + + assetTST.mint(user1, user1InitialBalance); + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + } + } + + // function testGulp() public { + // console2.log("#testGulp"); + // fourSixTwoSixAgg.gulp(); + // FourSixTwoSixAgg.ESRSlot memory ers = fourSixTwoSixAgg.getESRSlot(); + // assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + // assertEq(ers.interestLeft, 0); + + // vm.warp(block.timestamp + 2 days); + // fourSixTwoSixAgg.gulp(); + // assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + + // vm.warp(block.timestamp + 1 days); + // assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + // uint256 yield; + // { + // uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + // uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); + // uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; + // yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; + // assetTST.mint(address(eTST), yield); + // eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + // } + // vm.prank(user1); + // fourSixTwoSixAgg.harvest(address(eTST)); + + // assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + + // vm.warp(block.timestamp + 1 days); + // // interest per day 23.809523809523 + // assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); + // fourSixTwoSixAgg.gulp(); + // ers = fourSixTwoSixAgg.getESRSlot(); + // assertEq(ers.interestLeft, yield - 23809523809523809523); + + // // move close to end of smearing + // vm.warp(block.timestamp + 11 days); + // fourSixTwoSixAgg.gulp(); + // ers = fourSixTwoSixAgg.getESRSlot(); + // // assertEq(ers.interestLeft, yield - 23809523809523809523*11); + + // // mock a decrease of strategy balance by ers.interestLeft + // uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + // uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - ers.interestLeft; + // vm.mockCall( + // address(eTST), + // abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + // abi.encode(aggrCurrentStrategyBalanceAfterNegYield) + // ); + // vm.prank(user1); + // fourSixTwoSixAgg.harvest(address(eTST)); + // } + + function testGulp() public { + fourSixTwoSixAgg.gulp(); + FourSixTwoSixAgg.ESRSlot memory ers = fourSixTwoSixAgg.getESRSlot(); + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + assertEq(ers.interestLeft, 0); + + vm.warp(block.timestamp + 2 days); + fourSixTwoSixAgg.gulp(); + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + + vm.warp(block.timestamp + 1 days); + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + uint256 yield; + { + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); + uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; + yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; + assetTST.mint(address(eTST), yield); + eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + } + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + + vm.warp(block.timestamp + 1 days); + // interest per day 23.809523809523 + assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); + fourSixTwoSixAgg.gulp(); + ers = fourSixTwoSixAgg.getESRSlot(); + assertEq(ers.interestLeft, yield - 23809523809523809523); + + // move close to end of smearing + vm.warp(block.timestamp + 11 days); + fourSixTwoSixAgg.gulp(); + ers = fourSixTwoSixAgg.getESRSlot(); + // assertEq(ers.interestLeft, yield - 23809523809523809523*11); + + // mock a decrease of strategy balance by ers.interestLeft + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - (ers.interestLeft * 2); + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encode(aggrCurrentStrategyBalanceAfterNegYield) + ); + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + } +} From 0ce8f6592f085db2f11cf4fd77266707d79c2aad Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:44:23 +0100 Subject: [PATCH 116/316] clean --- src/FourSixTwoSixAgg.sol | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 57804dc0..d15dd4fb 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -224,6 +224,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @param _strategy Address of strategy to rebalance. function rebalance(address _strategy) external nonReentrant { _rebalance(_strategy); + + _gulp(); } /// @notice Rebalance multiple strategies. @@ -232,9 +234,11 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn for (uint256 i; i < _strategies.length; ++i) { _rebalance(_strategies[i]); } + + _gulp(); } - /// @notice Harvest positive yield. + /// @notice Harvest strategy. /// @param strategy address of strategy function harvest(address strategy) external nonReentrant { _harvest(strategy); @@ -242,6 +246,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _gulp(); } + /// @notice Harvest multiple strategie. + /// @param _strategies an array of strategy addresses. function harvestMultipleStrategies(address[] calldata _strategies) external nonReentrant { for (uint256 i; i < _strategies.length; ++i) { _harvest(_strategies[i]); @@ -340,6 +346,10 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn withdrawalQueue.pop(); } + function updateInterestAccrued() external returns (ESRSlot memory) { + return _updateInterestAccrued(); + } + function gulp() external nonReentrant { _gulp(); } @@ -366,7 +376,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @notice Return the accrued interest /// @return uint256 accrued interest function interestAccrued() external view returns (uint256) { - return interestAccruedFromCache(esrSlot); + return _interestAccruedFromCache(esrSlot); } /// @notice Transfers a certain amount of tokens to a recipient. @@ -418,7 +428,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn returns (uint256 shares) { // Move interest to totalAssetsDeposited - updateInterestAndReturnESRSlotCache(); + _updateInterestAccrued(); return super.withdraw(assets, receiver, owner); } @@ -432,13 +442,13 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn returns (uint256 assets) { // Move interest to totalAssetsDeposited - updateInterestAndReturnESRSlotCache(); + _updateInterestAccrued(); return super.redeem(shares, receiver, owner); } - function updateInterestAndReturnESRSlotCache() public returns (ESRSlot memory) { + function _updateInterestAccrued() internal returns (ESRSlot memory) { ESRSlot memory esrSlotCache = esrSlot; - uint256 accruedInterest = interestAccruedFromCache(esrSlotCache); + uint256 accruedInterest = _interestAccruedFromCache(esrSlotCache); // it's safe to down-cast because the accrued interest is a fraction of interest left esrSlotCache.interestLeft -= uint168(accruedInterest); esrSlotCache.lastInterestUpdate = uint40(block.timestamp); @@ -453,7 +463,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @notice Return the total amount of assets deposited, plus the accrued interest. /// @return uint256 total amount function totalAssets() public view override returns (uint256) { - return totalAssetsDeposited + interestAccruedFromCache(esrSlot); + return totalAssetsDeposited + _interestAccruedFromCache(esrSlot); } /// @notice get the total assets allocatable @@ -520,12 +530,15 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn super._withdraw(caller, receiver, owner, assets, shares); } + /// @dev gulp positive yield and increment the left interest function _gulp() internal { - ESRSlot memory esrSlotCache = updateInterestAndReturnESRSlotCache(); + ESRSlot memory esrSlotCache = _updateInterestAccrued(); if (totalAssetsDeposited == 0) return; uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft; - /// TODO return if toGulp == 0 + + if (toGulp == 0) return; + uint256 maxGulp = type(uint168).max - esrSlotCache.interestLeft; if (toGulp > maxGulp) toGulp = maxGulp; // cap interest, allowing the vault to function @@ -550,7 +563,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn // Harvest profits, also gulps and updates interest _harvest(_strategy); - _gulp(); Strategy memory strategyData = strategies[_strategy]; @@ -663,7 +675,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @dev Get accrued interest without updating it. /// @param esrSlotCache Cached esrSlot /// @return uint256 accrued interest - function interestAccruedFromCache(ESRSlot memory esrSlotCache) internal view returns (uint256) { + function _interestAccruedFromCache(ESRSlot memory esrSlotCache) internal view returns (uint256) { // If distribution ended, full amount is accrued if (block.timestamp > esrSlotCache.interestSmearEnd) { return esrSlotCache.interestLeft; From d55d61b1dd26b5085a98233b37310b46e0410a47 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:08:44 +0100 Subject: [PATCH 117/316] more tests --- test/unit/GulpTest.t.sol | 105 +++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 54 deletions(-) diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index c96cb3d1..3f6da94d 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -52,59 +52,57 @@ contract GulpTest is FourSixTwoSixAggBase { } } - // function testGulp() public { - // console2.log("#testGulp"); - // fourSixTwoSixAgg.gulp(); - // FourSixTwoSixAgg.ESRSlot memory ers = fourSixTwoSixAgg.getESRSlot(); - // assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - // assertEq(ers.interestLeft, 0); - - // vm.warp(block.timestamp + 2 days); - // fourSixTwoSixAgg.gulp(); - // assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - - // vm.warp(block.timestamp + 1 days); - // assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - // uint256 yield; - // { - // uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); - // uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); - // uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; - // yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; - // assetTST.mint(address(eTST), yield); - // eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); - // } - // vm.prank(user1); - // fourSixTwoSixAgg.harvest(address(eTST)); - - // assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - - // vm.warp(block.timestamp + 1 days); - // // interest per day 23.809523809523 - // assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); - // fourSixTwoSixAgg.gulp(); - // ers = fourSixTwoSixAgg.getESRSlot(); - // assertEq(ers.interestLeft, yield - 23809523809523809523); - - // // move close to end of smearing - // vm.warp(block.timestamp + 11 days); - // fourSixTwoSixAgg.gulp(); - // ers = fourSixTwoSixAgg.getESRSlot(); - // // assertEq(ers.interestLeft, yield - 23809523809523809523*11); - - // // mock a decrease of strategy balance by ers.interestLeft - // uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); - // uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - ers.interestLeft; - // vm.mockCall( - // address(eTST), - // abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), - // abi.encode(aggrCurrentStrategyBalanceAfterNegYield) - // ); - // vm.prank(user1); - // fourSixTwoSixAgg.harvest(address(eTST)); - // } - - function testGulp() public { + function testGulpAfterNegativeYieldEqualToInterestLeft() public { + fourSixTwoSixAgg.gulp(); + FourSixTwoSixAgg.ESRSlot memory ers = fourSixTwoSixAgg.getESRSlot(); + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + assertEq(ers.interestLeft, 0); + + vm.warp(block.timestamp + 2 days); + fourSixTwoSixAgg.gulp(); + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + + vm.warp(block.timestamp + 1 days); + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + uint256 yield; + { + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); + uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; + yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; + assetTST.mint(address(eTST), yield); + eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + } + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + + vm.warp(block.timestamp + 1 days); + // interest per day 23.809523809523 + assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); + fourSixTwoSixAgg.gulp(); + ers = fourSixTwoSixAgg.getESRSlot(); + assertEq(ers.interestLeft, yield - 23809523809523809523); + + // move close to end of smearing + vm.warp(block.timestamp + 11 days); + fourSixTwoSixAgg.gulp(); + ers = fourSixTwoSixAgg.getESRSlot(); + + // mock a decrease of strategy balance by ers.interestLeft + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - ers.interestLeft; + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encode(aggrCurrentStrategyBalanceAfterNegYield) + ); + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + } + + function testGulpAfterNegativeYieldBiggerThanInterestLeft() public { fourSixTwoSixAgg.gulp(); FourSixTwoSixAgg.ESRSlot memory ers = fourSixTwoSixAgg.getESRSlot(); assertEq(fourSixTwoSixAgg.interestAccrued(), 0); @@ -141,7 +139,6 @@ contract GulpTest is FourSixTwoSixAggBase { vm.warp(block.timestamp + 11 days); fourSixTwoSixAgg.gulp(); ers = fourSixTwoSixAgg.getESRSlot(); - // assertEq(ers.interestLeft, yield - 23809523809523809523*11); // mock a decrease of strategy balance by ers.interestLeft uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); From 970ead04526ae40ec8ad0f62c1b945f0fe0e06e8 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:18:16 +0100 Subject: [PATCH 118/316] clean --- src/FourSixTwoSixAgg.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index d15dd4fb..4ef2915d 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -10,8 +10,6 @@ import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; -import {Test, console2, stdError} from "forge-std/Test.sol"; - /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol @@ -246,7 +244,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _gulp(); } - /// @notice Harvest multiple strategie. + /// @notice Harvest multiple strategies. /// @param _strategies an array of strategy addresses. function harvestMultipleStrategies(address[] calldata _strategies) external nonReentrant { for (uint256 i; i < _strategies.length; ++i) { From 9545b5f83b3ba1e90434b0518d9ea2466e3e45bc Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:39:55 +0100 Subject: [PATCH 119/316] Add revert case --- src/BalanceForwarder.sol | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/BalanceForwarder.sol b/src/BalanceForwarder.sol index cd8e4aaf..0c7d1d64 100644 --- a/src/BalanceForwarder.sol +++ b/src/BalanceForwarder.sol @@ -10,6 +10,8 @@ import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; /// @notice A generic contract to integrate with https://github.com/euler-xyz/reward-streams abstract contract BalanceForwarder is IBalanceForwarder { error NotSupported(); + error AlreadyEnabled(); + error AlreadyDisabled(); IBalanceTracker public immutable balanceTracker; @@ -46,10 +48,13 @@ abstract contract BalanceForwarder is IBalanceForwarder { } function _enableBalanceForwarder(address _sender, uint256 _senderBalance) internal { - if (address(balanceTracker) == address(0)) revert NotSupported(); + address cachedBalanceTracker = address(balanceTracker); + + if (cachedBalanceTracker == address(0)) revert NotSupported(); + if (isBalanceForwarderEnabled[_sender]) revert AlreadyEnabled(); isBalanceForwarderEnabled[_sender] = true; - IBalanceTracker(balanceTracker).balanceTrackerHook(_sender, _senderBalance, false); + IBalanceTracker(cachedBalanceTracker).balanceTrackerHook(_sender, _senderBalance, false); emit EnableBalanceForwarder(_sender); } @@ -58,10 +63,13 @@ abstract contract BalanceForwarder is IBalanceForwarder { /// @dev Only the authenticated account can disable balance forwarding for itself /// @dev Should call the IBalanceTracker hook with the account's balance of 0 function _disableBalanceForwarder(address _sender) internal { - if (address(balanceTracker) == address(0)) revert NotSupported(); + address cachedBalanceTracker = address(balanceTracker); + + if (cachedBalanceTracker == address(0)) revert NotSupported(); + if (!isBalanceForwarderEnabled[_sender]) revert AlreadyDisabled(); isBalanceForwarderEnabled[_sender] = false; - IBalanceTracker(balanceTracker).balanceTrackerHook(_sender, 0, false); + IBalanceTracker(cachedBalanceTracker).balanceTrackerHook(_sender, 0, false); emit DisableBalanceForwarder(_sender); } From 62cef9e947deef587bd785c53221481544c7c60d Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:40:37 +0100 Subject: [PATCH 120/316] Push to withdrawalQueue --- src/FourSixTwoSixAgg.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 4ef2915d..57dbd787 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -139,6 +139,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn Strategy({allocated: 0, allocationPoints: uint120(_initialStrategiesAllocationPoints[i]), active: true}); _totalAllocationPoints += _initialStrategiesAllocationPoints[i]; + withdrawalQueue.push(_initialStrategies[i]); } totalAllocationPoints = _totalAllocationPoints; From 2eb3b234909a21115d47043403958088000f35e7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:41:59 +0100 Subject: [PATCH 121/316] Check that strategy is ERC4626 --- src/FourSixTwoSixAgg.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 57dbd787..d9936c6f 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -135,6 +135,10 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn uint256 _totalAllocationPoints = _initialCashAllocationPoints; for (uint256 i; i < _initialStrategies.length; ++i) { + if (IERC4626(_initialStrategies[i]).asset() != asset()) { + revert InvalidStrategyAsset(); + } + strategies[_initialStrategies[i]] = Strategy({allocated: 0, allocationPoints: uint120(_initialStrategiesAllocationPoints[i]), active: true}); From 3de4abaf6fa15b5450593eed75a3c11cc281afb1 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:30:29 +0100 Subject: [PATCH 122/316] use SafeCast; other fixes --- src/FourSixTwoSixAgg.sol | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index d9936c6f..d6a9b0b3 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -9,6 +9,7 @@ import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumera import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; +import {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol"; /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens @@ -16,6 +17,7 @@ import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; /// @dev inspired by Yearn v3 ❤️ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEnumerable { using SafeERC20 for IERC20; + using SafeCast for uint256; error Reentrancy(); error ArrayLengthMismatch(); @@ -32,6 +34,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn error MaxPerformanceFeeExceeded(); error FeeRecipientNotSet(); error FeeRecipientAlreadySet(); + error CanNotRemoveCashReserve(); uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; uint8 internal constant REENTRANCYLOCK__LOCKED = 2; @@ -130,22 +133,22 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn if (_initialCashAllocationPoints == 0) revert InitialAllocationPointsZero(); strategies[address(0)] = - Strategy({allocated: 0, allocationPoints: uint120(_initialCashAllocationPoints), active: true}); + Strategy({allocated: 0, allocationPoints: _initialCashAllocationPoints.toUint120(), active: true}); - uint256 _totalAllocationPoints = _initialCashAllocationPoints; + uint256 cachedTotalAllocationPoints = _initialCashAllocationPoints; for (uint256 i; i < _initialStrategies.length; ++i) { if (IERC4626(_initialStrategies[i]).asset() != asset()) { revert InvalidStrategyAsset(); } - + strategies[_initialStrategies[i]] = - Strategy({allocated: 0, allocationPoints: uint120(_initialStrategiesAllocationPoints[i]), active: true}); + Strategy({allocated: 0, allocationPoints: _initialStrategiesAllocationPoints[i].toUint120(), active: true}); - _totalAllocationPoints += _initialStrategiesAllocationPoints[i]; + cachedTotalAllocationPoints += _initialStrategiesAllocationPoints[i]; withdrawalQueue.push(_initialStrategies[i]); } - totalAllocationPoints = _totalAllocationPoints; + totalAllocationPoints = cachedTotalAllocationPoints; // Setup DEFAULT_ADMIN _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); @@ -273,7 +276,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert InactiveStrategy(); } - strategies[strategy].allocationPoints = uint120(newPoints); + strategies[strategy].allocationPoints = newPoints.toUint120(); totalAllocationPoints = totalAllocationPoints + newPoints - strategyDataCache.allocationPoints; } @@ -315,7 +318,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert StrategyAlreadyExist(); } - strategies[strategy] = Strategy({allocated: 0, allocationPoints: uint120(allocationPoints), active: true}); + strategies[strategy] = Strategy({allocated: 0, allocationPoints: allocationPoints.toUint120(), active: true}); totalAllocationPoints += allocationPoints; withdrawalQueue.push(strategy); @@ -326,6 +329,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @dev Can only be called by an address that have the STRATEGY_REMOVER_ROLE /// @param strategy Address of the strategy function removeStrategy(address strategy) external nonReentrant onlyRole(STRATEGY_REMOVER_ROLE) { + if (strategy == address(0)) revert CanNotRemoveCashReserve(); + Strategy storage strategyStorage = strategies[strategy]; if (!strategyStorage.active) { @@ -343,6 +348,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn if (withdrawalQueue[i] == strategy) { withdrawalQueue[i] = withdrawalQueue[lastStrategyIndex]; withdrawalQueue[lastStrategyIndex] = strategy; + + break; } } From 396afda350c87d03937a2f953f8a481508248fc7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:45:43 +0100 Subject: [PATCH 123/316] refactor _withdraw() --- src/FourSixTwoSixAgg.sol | 42 ++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index d6a9b0b3..514ab8cb 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -142,8 +142,11 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert InvalidStrategyAsset(); } - strategies[_initialStrategies[i]] = - Strategy({allocated: 0, allocationPoints: _initialStrategiesAllocationPoints[i].toUint120(), active: true}); + strategies[_initialStrategies[i]] = Strategy({ + allocated: 0, + allocationPoints: _initialStrategiesAllocationPoints[i].toUint120(), + active: true + }); cachedTotalAllocationPoints += _initialStrategiesAllocationPoints[i]; withdrawalQueue.push(_initialStrategies[i]); @@ -500,14 +503,25 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn override { totalAssetsDeposited -= assets; - uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); + + if (assetsRetrieved < assets) assetsRetrieved = _withdrawFromStrategies(assetsRetrieved, assets); + if (assetsRetrieved < assets) { + revert NotEnoughAssets(); + } + + _gulp(); + + super._withdraw(caller, receiver, owner, assets, shares); + } + + /// @dev Withdraw needed asset amount from strategies. + /// @param _currentBalance Aggregator asset balance. + /// @param _targetBalance target balance. + /// @return uint256 current balance after withdraw. + function _withdrawFromStrategies(uint256 _currentBalance, uint256 _targetBalance) internal returns (uint256) { uint256 numStrategies = withdrawalQueue.length; for (uint256 i; i < numStrategies; ++i) { - if (assetsRetrieved >= assets) { - break; - } - IERC4626 strategy = IERC4626(withdrawalQueue[i]); _harvest(address(strategy)); @@ -517,7 +531,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn uint256 sharesBalance = strategy.balanceOf(address(this)); uint256 underlyingBalance = strategy.convertToAssets(sharesBalance); - uint256 desiredAssets = assets - assetsRetrieved; + uint256 desiredAssets = _targetBalance - _currentBalance; uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; // Update allocated assets @@ -525,19 +539,17 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn totalAllocated -= withdrawAmount; // update assetsRetrieved - assetsRetrieved += withdrawAmount; + _currentBalance += withdrawAmount; // Do actual withdraw from strategy strategy.withdraw(withdrawAmount, address(this), address(this)); - } - - _gulp(); - if (assetsRetrieved < assets) { - revert NotEnoughAssets(); + if (_currentBalance >= _targetBalance) { + break; + } } - super._withdraw(caller, receiver, owner, assets, shares); + return _currentBalance; } /// @dev gulp positive yield and increment the left interest From 33200872cd9f848b7e8ef113a112d4d1560fe1bf Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 4 Jun 2024 17:16:06 +0100 Subject: [PATCH 124/316] use max functions; ignore balance tracker hooks in self-transfer --- src/FourSixTwoSixAgg.sol | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 514ab8cb..7839e771 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -528,8 +528,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn Strategy storage strategyStorage = strategies[address(strategy)]; - uint256 sharesBalance = strategy.balanceOf(address(this)); - uint256 underlyingBalance = strategy.convertToAssets(sharesBalance); + uint256 underlyingBalance = strategy.maxWithdraw(address(this)); uint256 desiredAssets = _targetBalance - _currentBalance; uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; @@ -641,8 +640,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn Strategy memory strategyData = strategies[strategy]; if (strategyData.allocated == 0) return; - uint256 sharesBalance = IERC4626(strategy).balanceOf(address(this)); - uint256 underlyingBalance = IERC4626(strategy).convertToAssets(sharesBalance); + + uint256 underlyingBalance = IERC4626(strategy).maxWithdraw(address(this)); if (underlyingBalance == strategyData.allocated) { return; @@ -685,6 +684,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @param from Address sending the amount /// @param to Address receiving the amount function _afterTokenTransfer(address from, address to, uint256 /*amount*/ ) internal override { + if (from == to) return; + if ((from != address(0)) && (isBalanceForwarderEnabled[from])) { balanceTracker.balanceTrackerHook(from, super.balanceOf(from), false); } From c065a0ded1e8c4c346685ee0889571ec0fa39060 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 4 Jun 2024 20:01:20 +0100 Subject: [PATCH 125/316] update timestamp check --- src/FourSixTwoSixAgg.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 7839e771..be6319b4 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -685,7 +685,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @param to Address receiving the amount function _afterTokenTransfer(address from, address to, uint256 /*amount*/ ) internal override { if (from == to) return; - + if ((from != address(0)) && (isBalanceForwarderEnabled[from])) { balanceTracker.balanceTrackerHook(from, super.balanceOf(from), false); } @@ -700,7 +700,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @return uint256 accrued interest function _interestAccruedFromCache(ESRSlot memory esrSlotCache) internal view returns (uint256) { // If distribution ended, full amount is accrued - if (block.timestamp > esrSlotCache.interestSmearEnd) { + if (block.timestamp >= esrSlotCache.interestSmearEnd) { return esrSlotCache.interestLeft; } From 8ea4e4d2b468cc9ea4fcb55a3346ab6384ed98bd Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:28:04 +0100 Subject: [PATCH 126/316] fix --- src/FourSixTwoSixAgg.sol | 5 +++-- test/unit/HarvestTest.t.sol | 16 +++++----------- test/unit/RebalanceTest.t.sol | 11 ++++++----- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index be6319b4..7e75b025 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -578,11 +578,12 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// - If all the available cash is greater than the max deposit, deposit the max deposit /// @param _strategy Address of strategy to rebalance. function _rebalance(address _strategy) internal { + console2.log("rebalanciiiing"); + if (_strategy == address(0)) { return; //nothing to rebalance as this is the cash reserve } - // Harvest profits, also gulps and updates interest _harvest(_strategy); Strategy memory strategyData = strategies[_strategy]; @@ -603,7 +604,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn } IERC4626(_strategy).withdraw(toWithdraw, address(this), address(this)); - strategies[_strategy].allocated = uint120(currentAllocation - toWithdraw); + strategies[_strategy].allocated = (currentAllocation - toWithdraw).toUint120(); totalAllocated -= toWithdraw; } else if (currentAllocation < targetAllocation) { // Deposit diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index e5039756..c660dac4 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -77,18 +77,14 @@ contract HarvestTest is FourSixTwoSixAggBase { ); assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) > strategyBefore.allocated); + uint256 expectedAllocated = eTST.maxWithdraw(address(fourSixTwoSixAgg)); vm.prank(user1); fourSixTwoSixAgg.harvest(address(eTST)); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedAllocated); assertEq( - (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, - eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) - ); - assertEq( - fourSixTwoSixAgg.totalAllocated(), - totalAllocatedBefore - + (eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) - strategyBefore.allocated) + fourSixTwoSixAgg.totalAllocated(), totalAllocatedBefore + (expectedAllocated - strategyBefore.allocated) ); } @@ -125,8 +121,7 @@ contract HarvestTest is FourSixTwoSixAggBase { FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated); - uint256 negativeYield = - strategyBefore.allocated - eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(fourSixTwoSixAgg)); uint256 user1SharesBefore = fourSixTwoSixAgg.balanceOf(user1); uint256 user1SocializedLoss = user1SharesBefore * negativeYield / fourSixTwoSixAgg.totalSupply(); @@ -169,8 +164,7 @@ contract HarvestTest is FourSixTwoSixAggBase { FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated); - uint256 negativeYield = - strategyBefore.allocated - eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(fourSixTwoSixAgg)); uint256 user1SharesBefore = fourSixTwoSixAgg.balanceOf(user1); uint256 user1SocializedLoss = user1SharesBefore * negativeYield / fourSixTwoSixAgg.totalSupply(); uint256 user2SharesBefore = fourSixTwoSixAgg.balanceOf(user2); diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index c4a8ba97..cbac406e 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -266,6 +266,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { ); } + /// TODO: update this test function testRebalanceByWithdrawingWhenToWithdrawIsGreaterThanMaxWithdraw() public { uint256 amountToDeposit = 10000e18; @@ -315,10 +316,10 @@ contract RebalanceTest is FourSixTwoSixAggBase { vm.prank(user1); fourSixTwoSixAgg.rebalance(address(eTST)); - assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); - assertEq( - eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated - eTSTMaxWithdraw - ); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated - eTSTMaxWithdraw); + // assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); + // assertEq( + // eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated - eTSTMaxWithdraw + // ); + // assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated - eTSTMaxWithdraw); } } From f76a87a79a8bc753f0ab39fd9be88f1fe3ab0d7f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:30:21 +0100 Subject: [PATCH 127/316] fix --- src/FourSixTwoSixAgg.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 7e75b025..a862de62 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -578,8 +578,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// - If all the available cash is greater than the max deposit, deposit the max deposit /// @param _strategy Address of strategy to rebalance. function _rebalance(address _strategy) internal { - console2.log("rebalanciiiing"); - if (_strategy == address(0)) { return; //nothing to rebalance as this is the cash reserve } From 9956613c247cee3dcbbeee684b2f03e427a39a32 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 6 Jun 2024 11:48:03 +0100 Subject: [PATCH 128/316] feat: add events --- src/FourSixTwoSixAgg.sol | 70 ++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index a862de62..03d49413 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -92,6 +92,15 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn bool active; } + event SetFeeRecipient(address indexed oldRecipient, address indexed newRecipient); + event SetPerformanceFee(uint256 oldFee, uint256 newFee); + event OptInStrategyRewards(address indexed strategy); + event OptOutStrategyRewards(address indexed strategy); + event Rebalance( + address indexed strategy, uint256 currentAllocation, uint256 targetAllocation, uint256 amountToRebalance + ); + event Gulp(uint256 interestLeft, uint256 interestSmearEnd); + /// @notice Modifier to require an account status check on the EVC. /// @dev Calls `requireAccountStatusCheck` function from EVC for the specified account after the function body. /// @param account The address of the account to check. @@ -169,6 +178,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn function setFeeRecipient(address _newFeeRecipient) external onlyRole(TREASURY_MANAGER_ROLE) { if (_newFeeRecipient == feeRecipient) revert FeeRecipientAlreadySet(); + emit SetFeeRecipient(feeRecipient, _newFeeRecipient); + feeRecipient = _newFeeRecipient; } @@ -179,6 +190,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn if (feeRecipient == address(0)) revert FeeRecipientNotSet(); if (_newFee == performanceFee) revert PerformanceFeeAlreadySet(); + emit SetPerformanceFee(performanceFee, _newFee); + performanceFee = _newFee; } @@ -188,12 +201,16 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn if (!strategies[_strategy].active) revert InactiveStrategy(); IBalanceForwarder(_strategy).enableBalanceForwarder(); + + emit OptInStrategyRewards(_strategy); } /// @notice Opt out of strategy rewards /// @param _strategy Strategy address function optOutStrategyRewards(address _strategy) external onlyRole(TREASURY_MANAGER_ROLE) { IBalanceForwarder(_strategy).disableBalanceForwarder(); + + emit OptOutStrategyRewards(_strategy); } /// @notice Claim a specific strategy rewards @@ -568,6 +585,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn // write esrSlotCache back to storage in a single SSTORE esrSlot = esrSlotCache; + + emit Gulp(esrSlotCache.interestLeft, esrSlotCache.interestSmearEnd); } /// @notice Rebalance strategy allocation. @@ -592,18 +611,19 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; uint256 currentAllocation = strategyData.allocated; + uint256 amountToRebalance; if (currentAllocation > targetAllocation) { // Withdraw - uint256 toWithdraw = currentAllocation - targetAllocation; + amountToRebalance = currentAllocation - targetAllocation; uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(address(this)); - if (toWithdraw > maxWithdraw) { - toWithdraw = maxWithdraw; + if (amountToRebalance > maxWithdraw) { + amountToRebalance = maxWithdraw; } - IERC4626(_strategy).withdraw(toWithdraw, address(this), address(this)); - strategies[_strategy].allocated = (currentAllocation - toWithdraw).toUint120(); - totalAllocated -= toWithdraw; + IERC4626(_strategy).withdraw(amountToRebalance, address(this), address(this)); + strategies[_strategy].allocated = (currentAllocation - amountToRebalance).toUint120(); + totalAllocated -= amountToRebalance; } else if (currentAllocation < targetAllocation) { // Deposit uint256 targetCash = @@ -613,48 +633,54 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn // Calculate available cash to put in strategies uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; - uint256 toDeposit = targetAllocation - currentAllocation; - if (toDeposit > cashAvailable) { - toDeposit = cashAvailable; + amountToRebalance = targetAllocation - currentAllocation; + if (amountToRebalance > cashAvailable) { + amountToRebalance = cashAvailable; } uint256 maxDeposit = IERC4626(_strategy).maxDeposit(address(this)); - if (toDeposit > maxDeposit) { - toDeposit = maxDeposit; + if (amountToRebalance > maxDeposit) { + amountToRebalance = maxDeposit; } - if (toDeposit == 0) { + if (amountToRebalance == 0) { return; // No cash to deposit } // Do required approval (safely) and deposit - IERC20(asset()).safeApprove(_strategy, toDeposit); - IERC4626(_strategy).deposit(toDeposit, address(this)); - strategies[_strategy].allocated = uint120(currentAllocation + toDeposit); - totalAllocated += toDeposit; + IERC20(asset()).safeApprove(_strategy, amountToRebalance); + IERC4626(_strategy).deposit(amountToRebalance, address(this)); + strategies[_strategy].allocated = uint120(currentAllocation + amountToRebalance); + totalAllocated += amountToRebalance; } + + emit Rebalance(_strategy, currentAllocation, targetAllocation, amountToRebalance); } - function _harvest(address strategy) internal { - Strategy memory strategyData = strategies[strategy]; + event Harvest( + address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount, uint168 interestLeft + ); + + function _harvest(address _strategy) internal { + Strategy memory strategyData = strategies[_strategy]; if (strategyData.allocated == 0) return; - uint256 underlyingBalance = IERC4626(strategy).maxWithdraw(address(this)); + uint256 underlyingBalance = IERC4626(_strategy).maxWithdraw(address(this)); if (underlyingBalance == strategyData.allocated) { return; } else if (underlyingBalance > strategyData.allocated) { // There's yield! uint256 yield = underlyingBalance - strategyData.allocated; - strategies[strategy].allocated = uint120(underlyingBalance); + strategies[_strategy].allocated = uint120(underlyingBalance); totalAllocated += yield; _accruePerformanceFee(yield); } else { uint256 loss = strategyData.allocated - underlyingBalance; - strategies[strategy].allocated = uint120(underlyingBalance); + strategies[_strategy].allocated = uint120(underlyingBalance); totalAllocated -= loss; ESRSlot memory esrSlotCache = esrSlot; @@ -666,6 +692,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn } esrSlot = esrSlotCache; } + + emit Harvest(_strategy, underlyingBalance, strategyData.allocated, esrSlotCache.interestLeft); } function _accruePerformanceFee(uint256 _yield) internal { From 648affd1af3f48f6270ba666670e3d11b8251fe5 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 6 Jun 2024 11:54:15 +0100 Subject: [PATCH 129/316] fix build --- src/FourSixTwoSixAgg.sol | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 03d49413..f47a4f48 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -100,6 +100,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn address indexed strategy, uint256 currentAllocation, uint256 targetAllocation, uint256 amountToRebalance ); event Gulp(uint256 interestLeft, uint256 interestSmearEnd); + event Harvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); /// @notice Modifier to require an account status check on the EVC. /// @dev Calls `requireAccountStatusCheck` function from EVC for the specified account after the function body. @@ -657,10 +658,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn emit Rebalance(_strategy, currentAllocation, targetAllocation, amountToRebalance); } - event Harvest( - address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount, uint168 interestLeft - ); - function _harvest(address _strategy) internal { Strategy memory strategyData = strategies[_strategy]; @@ -693,7 +690,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn esrSlot = esrSlotCache; } - emit Harvest(_strategy, underlyingBalance, strategyData.allocated, esrSlotCache.interestLeft); + emit Harvest(_strategy, underlyingBalance, strategyData.allocated); } function _accruePerformanceFee(uint256 _yield) internal { From 019ff45d0059cdeb56f682dc6dd04bc507d96e89 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:25:13 +0100 Subject: [PATCH 130/316] add more events --- src/FourSixTwoSixAgg.sol | 81 ++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 29 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index f47a4f48..66ba6442 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -101,6 +101,11 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn ); event Gulp(uint256 interestLeft, uint256 interestSmearEnd); event Harvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); + event AdjustAllocationPoints(address indexed strategy, uint256 oldPoints, uint256 newPoints); + event ReorderWithdrawalQueue(uint8 index1, uint8 index2); + event AddStrategy(address indexed strategy, uint256 allocationPoints); + event RemoveStrategy(address indexed _strategy); + event AccruePerformanceFee(address indexed feeRecipient, uint256 performanceFee, uint256 yield, uint256 feeShares); /// @notice Modifier to require an account status check on the EVC. /// @dev Calls `requireAccountStatusCheck` function from EVC for the specified account after the function body. @@ -284,75 +289,81 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @notice Adjust a certain strategy's allocation points. /// @dev Can only be called by an address that have the ALLOCATION_ADJUSTER_ROLE - /// @param strategy address of strategy - /// @param newPoints new strategy's points - function adjustAllocationPoints(address strategy, uint256 newPoints) + /// @param _strategy address of strategy + /// @param _newPoints new strategy's points + function adjustAllocationPoints(address _strategy, uint256 _newPoints) external nonReentrant onlyRole(ALLOCATION_ADJUSTER_ROLE) { - Strategy memory strategyDataCache = strategies[strategy]; + Strategy memory strategyDataCache = strategies[_strategy]; if (!strategyDataCache.active) { revert InactiveStrategy(); } - strategies[strategy].allocationPoints = newPoints.toUint120(); - totalAllocationPoints = totalAllocationPoints + newPoints - strategyDataCache.allocationPoints; + strategies[_strategy].allocationPoints = _newPoints.toUint120(); + totalAllocationPoints = totalAllocationPoints + _newPoints - strategyDataCache.allocationPoints; + + emit AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); } /// @notice Swap two strategies indexes in the withdrawal queue. /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_REORDERER_ROLE. - /// @param index1 index of first strategy - /// @param index2 index of second strategy - function reorderWithdrawalQueue(uint8 index1, uint8 index2) + /// @param _index1 index of first strategy + /// @param _index2 index of second strategy + function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external nonReentrant onlyRole(WITHDRAW_QUEUE_REORDERER_ROLE) { uint256 length = withdrawalQueue.length; - if (index1 >= length || index2 >= length) { + if (_index1 >= length || _index2 >= length) { revert OutOfBounds(); } - if (index1 == index2) { + if (_index1 == _index2) { revert SameIndexes(); } - (withdrawalQueue[index1], withdrawalQueue[index2]) = (withdrawalQueue[index2], withdrawalQueue[index1]); + (withdrawalQueue[_index1], withdrawalQueue[_index2]) = (withdrawalQueue[_index2], withdrawalQueue[_index1]); + + emit ReorderWithdrawalQueue(_index1, _index2); } /// @notice Add new strategy with it's allocation points. /// @dev Can only be called by an address that have STRATEGY_ADDER_ROLE. - /// @param strategy Address of the strategy - /// @param allocationPoints Strategy's allocation points - function addStrategy(address strategy, uint256 allocationPoints) + /// @param _strategy Address of the strategy + /// @param _allocationPoints Strategy's allocation points + function addStrategy(address _strategy, uint256 _allocationPoints) external nonReentrant onlyRole(STRATEGY_ADDER_ROLE) { - if (IERC4626(strategy).asset() != asset()) { + if (IERC4626(_strategy).asset() != asset()) { revert InvalidStrategyAsset(); } - if (strategies[strategy].active) { + if (strategies[_strategy].active) { revert StrategyAlreadyExist(); } - strategies[strategy] = Strategy({allocated: 0, allocationPoints: allocationPoints.toUint120(), active: true}); + strategies[_strategy] = Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true}); + + totalAllocationPoints += _allocationPoints; + withdrawalQueue.push(_strategy); - totalAllocationPoints += allocationPoints; - withdrawalQueue.push(strategy); + emit AddStrategy(_strategy, _allocationPoints); } /// @notice Remove strategy and set its allocation points to zero. /// @dev This function does not pull funds, `harvest()` needs to be called to withdraw /// @dev Can only be called by an address that have the STRATEGY_REMOVER_ROLE - /// @param strategy Address of the strategy - function removeStrategy(address strategy) external nonReentrant onlyRole(STRATEGY_REMOVER_ROLE) { - if (strategy == address(0)) revert CanNotRemoveCashReserve(); + /// @param _strategy Address of the strategy + function removeStrategy(address _strategy) external nonReentrant onlyRole(STRATEGY_REMOVER_ROLE) { + if (_strategy == address(0)) revert CanNotRemoveCashReserve(); - Strategy storage strategyStorage = strategies[strategy]; + Strategy storage strategyStorage = strategies[_strategy]; if (!strategyStorage.active) { revert AlreadyRemoved(); @@ -366,21 +377,26 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn uint256 lastStrategyIndex = withdrawalQueue.length - 1; for (uint256 i = 0; i < lastStrategyIndex; ++i) { - if (withdrawalQueue[i] == strategy) { + if (withdrawalQueue[i] == _strategy) { withdrawalQueue[i] = withdrawalQueue[lastStrategyIndex]; - withdrawalQueue[lastStrategyIndex] = strategy; + withdrawalQueue[lastStrategyIndex] = _strategy; break; } } withdrawalQueue.pop(); + + emit RemoveStrategy(_strategy); } + /// @notice update accrued interest + /// @return struct ESRSlot struct function updateInterestAccrued() external returns (ESRSlot memory) { return _updateInterestAccrued(); } + /// @notice gulp positive harvest yield function gulp() external nonReentrant { _gulp(); } @@ -477,6 +493,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn return super.redeem(shares, receiver, owner); } + /// @notice update accrued interest. + /// @return struct ESRSlot struct. function _updateInterestAccrued() internal returns (ESRSlot memory) { ESRSlot memory esrSlotCache = esrSlot; uint256 accruedInterest = _interestAccruedFromCache(esrSlotCache); @@ -694,13 +712,18 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn } function _accruePerformanceFee(uint256 _yield) internal { - if (feeRecipient == address(0) || performanceFee == 0) return; + address cachedFeeRecipient = feeRecipient; + uint256 cachedPerformanceFee = performanceFee; + + if (cachedFeeRecipient == address(0) || cachedPerformanceFee == 0) return; // `feeAssets` will be rounded down to 0 if `yield * performanceFee < 1e18`. - uint256 feeAssets = Math.mulDiv(_yield, performanceFee, 1e18, Math.Rounding.Down); + uint256 feeAssets = Math.mulDiv(_yield, cachedPerformanceFee, 1e18, Math.Rounding.Down); uint256 feeShares = _convertToShares(feeAssets, Math.Rounding.Down); - if (feeShares != 0) _mint(feeRecipient, feeShares); + if (feeShares != 0) _mint(cachedFeeRecipient, feeShares); + + emit AccruePerformanceFee(cachedFeeRecipient, cachedPerformanceFee, _yield, feeShares); } /// @dev Override _afterTokenTransfer hook to call IBalanceTracker.balanceTrackerHook() From e5f9f3a48a6aa5f1a5e31dce847473a9d11f2869 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:41:50 +0100 Subject: [PATCH 131/316] feat: init cap impl --- src/FourSixTwoSixAgg.sol | 61 +++++++++++++++++--------- test/common/FourSixTwoSixAggBase.t.sol | 42 +++++++++--------- test/e2e/BalanceForwarderE2ETest.t.sol | 14 +++--- 3 files changed, 69 insertions(+), 48 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 66ba6442..df056e06 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -40,17 +40,17 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn uint8 internal constant REENTRANCYLOCK__LOCKED = 2; // Roles - bytes32 public constant ALLOCATION_ADJUSTER_ROLE = keccak256("ALLOCATION_ADJUSTER_ROLE"); - bytes32 public constant ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE = keccak256("ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE"); - bytes32 public constant WITHDRAW_QUEUE_REORDERER_ROLE = keccak256("WITHDRAW_QUEUE_REORDERER_ROLE"); - bytes32 public constant WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE = - keccak256("WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE"); + bytes32 public constant STRATEGY_MANAGER_ROLE = keccak256("STRATEGY_MANAGER_ROLE"); + bytes32 public constant STRATEGY_MANAGER_ROLE_ADMINROLE = keccak256("STRATEGY_MANAGER_ROLE_ADMINROLE"); + bytes32 public constant WITHDRAW_QUEUE_MANAGER_ROLE = keccak256("WITHDRAW_QUEUE_MANAGER_ROLE"); + bytes32 public constant WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE = + keccak256("WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE"); bytes32 public constant STRATEGY_ADDER_ROLE = keccak256("STRATEGY_ADDER_ROLE"); - bytes32 public constant STRATEGY_ADDER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_ADDER_ROLE_ADMIN_ROLE"); + bytes32 public constant STRATEGY_ADDER_ROLE_ADMINROLE = keccak256("STRATEGY_ADDER_ROLE_ADMINROLE"); bytes32 public constant STRATEGY_REMOVER_ROLE = keccak256("STRATEGY_REMOVER_ROLE"); - bytes32 public constant STRATEGY_REMOVER_ROLE_ADMIN_ROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMIN_ROLE"); + bytes32 public constant STRATEGY_REMOVER_ROLE_ADMINROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMINROLE"); bytes32 public constant TREASURY_MANAGER_ROLE = keccak256("TREASURY_MANAGER_ROLE"); - bytes32 public constant TREASURY_MANAGER_ROLE_ADMIN_ROLE = keccak256("TREASURY_MANAGER_ROLE_ADMIN_ROLE"); + bytes32 public constant TREASURY_MANAGER_ROLE_ADMINROLE = keccak256("TREASURY_MANAGER_ROLE_ADMINROLE"); /// @dev The maximum performanceFee the vault can have is 50% uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; @@ -86,10 +86,12 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// allocated: amount of asset deposited into strategy /// allocationPoints: number of points allocated to this strategy /// active: a boolean to indice if this strategy is active or not + /// cap: an optional cap in terms of deposited underlying asset. By default, it is set to 0(not activated) struct Strategy { uint120 allocated; uint120 allocationPoints; bool active; + uint120 cap; } event SetFeeRecipient(address indexed oldRecipient, address indexed newRecipient); @@ -148,7 +150,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn if (_initialCashAllocationPoints == 0) revert InitialAllocationPointsZero(); strategies[address(0)] = - Strategy({allocated: 0, allocationPoints: _initialCashAllocationPoints.toUint120(), active: true}); + Strategy({allocated: 0, allocationPoints: _initialCashAllocationPoints.toUint120(), active: true, cap: 0}); uint256 cachedTotalAllocationPoints = _initialCashAllocationPoints; @@ -160,7 +162,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn strategies[_initialStrategies[i]] = Strategy({ allocated: 0, allocationPoints: _initialStrategiesAllocationPoints[i].toUint120(), - active: true + active: true, + cap: 0 }); cachedTotalAllocationPoints += _initialStrategiesAllocationPoints[i]; @@ -172,11 +175,11 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); // Setup role admins - _setRoleAdmin(ALLOCATION_ADJUSTER_ROLE, ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE); - _setRoleAdmin(WITHDRAW_QUEUE_REORDERER_ROLE, WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE); - _setRoleAdmin(STRATEGY_ADDER_ROLE, STRATEGY_ADDER_ROLE_ADMIN_ROLE); - _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMIN_ROLE); - _setRoleAdmin(TREASURY_MANAGER_ROLE, TREASURY_MANAGER_ROLE_ADMIN_ROLE); + _setRoleAdmin(STRATEGY_MANAGER_ROLE, STRATEGY_MANAGER_ROLE_ADMINROLE); + _setRoleAdmin(WITHDRAW_QUEUE_MANAGER_ROLE, WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE); + _setRoleAdmin(STRATEGY_ADDER_ROLE, STRATEGY_ADDER_ROLE_ADMINROLE); + _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMINROLE); + _setRoleAdmin(TREASURY_MANAGER_ROLE, TREASURY_MANAGER_ROLE_ADMINROLE); } /// @notice Set performance fee recipient address @@ -288,13 +291,13 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn } /// @notice Adjust a certain strategy's allocation points. - /// @dev Can only be called by an address that have the ALLOCATION_ADJUSTER_ROLE + /// @dev Can only be called by an address that have the STRATEGY_MANAGER_ROLE /// @param _strategy address of strategy /// @param _newPoints new strategy's points function adjustAllocationPoints(address _strategy, uint256 _newPoints) external nonReentrant - onlyRole(ALLOCATION_ADJUSTER_ROLE) + onlyRole(STRATEGY_MANAGER_ROLE) { Strategy memory strategyDataCache = strategies[_strategy]; @@ -308,14 +311,32 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn emit AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); } + event SetStrategyCap(address indexed strategy, uint256 cap); + + function setStrategyCap(address _strategy, uint256 _cap) + external + nonReentrant + onlyRole(STRATEGY_MANAGER_ROLE) + { + Strategy memory strategyDataCache = strategies[_strategy]; + + if (!strategyDataCache.active) { + revert InactiveStrategy(); + } + + strategies[_strategy].cap = _cap.toUint120(); + + emit SetStrategyCap(_strategy, _cap); + } + /// @notice Swap two strategies indexes in the withdrawal queue. - /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_REORDERER_ROLE. + /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER_ROLE. /// @param _index1 index of first strategy /// @param _index2 index of second strategy function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external nonReentrant - onlyRole(WITHDRAW_QUEUE_REORDERER_ROLE) + onlyRole(WITHDRAW_QUEUE_MANAGER_ROLE) { uint256 length = withdrawalQueue.length; if (_index1 >= length || _index2 >= length) { @@ -348,7 +369,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert StrategyAlreadyExist(); } - strategies[_strategy] = Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true}); + strategies[_strategy] = Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); totalAllocationPoints += _allocationPoints; withdrawalQueue.push(_strategy); diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 389a2528..5c2dc191 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -34,15 +34,15 @@ contract FourSixTwoSixAggBase is EVaultTestBase { ); // grant admin roles to deployer - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMINROLE(), deployer); // grant roles to manager - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE(), manager); @@ -58,34 +58,34 @@ contract FourSixTwoSixAggBase is EVaultTestBase { assertEq(cashReserve.active, true); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE()), - fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE()), + fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE() ); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE()), - fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE()), + fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE() ); assertEq( fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE()), - fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE() + fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE() ); assertEq( fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE()), - fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE() + fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE() ); assertEq( fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE()), - fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMIN_ROLE() + fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMINROLE() ); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMIN_ROLE(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMINROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE(), manager)); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 746ae0b3..33927d78 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -35,15 +35,15 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { ); // grant admin roles to deployer - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE_ADMIN_ROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE_ADMIN_ROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMIN_ROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMIN_ROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMIN_ROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMINROLE(), deployer); // grant roles to manager - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATION_ADJUSTER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_REORDERER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE(), manager); From e171136b4fb66af8aa5ce336eb0dcc1047189297 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:33:06 +0300 Subject: [PATCH 132/316] add tests --- src/FourSixTwoSixAgg.sol | 32 ++++++++++----------- test/e2e/StrategyCapE2ETest.t.sol | 46 +++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 test/e2e/StrategyCapE2ETest.t.sol diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index df056e06..1b9bc81f 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -43,8 +43,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn bytes32 public constant STRATEGY_MANAGER_ROLE = keccak256("STRATEGY_MANAGER_ROLE"); bytes32 public constant STRATEGY_MANAGER_ROLE_ADMINROLE = keccak256("STRATEGY_MANAGER_ROLE_ADMINROLE"); bytes32 public constant WITHDRAW_QUEUE_MANAGER_ROLE = keccak256("WITHDRAW_QUEUE_MANAGER_ROLE"); - bytes32 public constant WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE = - keccak256("WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE"); + bytes32 public constant WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE = keccak256("WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE"); bytes32 public constant STRATEGY_ADDER_ROLE = keccak256("STRATEGY_ADDER_ROLE"); bytes32 public constant STRATEGY_ADDER_ROLE_ADMINROLE = keccak256("STRATEGY_ADDER_ROLE_ADMINROLE"); bytes32 public constant STRATEGY_REMOVER_ROLE = keccak256("STRATEGY_REMOVER_ROLE"); @@ -313,11 +312,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn event SetStrategyCap(address indexed strategy, uint256 cap); - function setStrategyCap(address _strategy, uint256 _cap) - external - nonReentrant - onlyRole(STRATEGY_MANAGER_ROLE) - { + function setStrategyCap(address _strategy, uint256 _cap) external nonReentrant onlyRole(STRATEGY_MANAGER_ROLE) { Strategy memory strategyDataCache = strategies[_strategy]; if (!strategyDataCache.active) { @@ -369,7 +364,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert StrategyAlreadyExist(); } - strategies[_strategy] = Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); + strategies[_strategy] = + Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); totalAllocationPoints += _allocationPoints; withdrawalQueue.push(_strategy); @@ -645,16 +641,20 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn Strategy memory strategyData = strategies[_strategy]; + // no rebalance if strategy have an allocated amount greater than cap + if ((strategyData.cap > 0) && (strategyData.allocated > strategyData.cap)) return; + uint256 totalAllocationPointsCache = totalAllocationPoints; uint256 totalAssetsAllocatableCache = totalAssetsAllocatable(); uint256 targetAllocation = totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; - uint256 currentAllocation = strategyData.allocated; + + if ((strategyData.cap > 0) && (targetAllocation > strategyData.cap)) targetAllocation = strategyData.cap; uint256 amountToRebalance; - if (currentAllocation > targetAllocation) { + if (strategyData.allocated > targetAllocation) { // Withdraw - amountToRebalance = currentAllocation - targetAllocation; + amountToRebalance = strategyData.allocated - targetAllocation; uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(address(this)); if (amountToRebalance > maxWithdraw) { @@ -662,9 +662,9 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn } IERC4626(_strategy).withdraw(amountToRebalance, address(this), address(this)); - strategies[_strategy].allocated = (currentAllocation - amountToRebalance).toUint120(); + strategies[_strategy].allocated = (strategyData.allocated - amountToRebalance).toUint120(); totalAllocated -= amountToRebalance; - } else if (currentAllocation < targetAllocation) { + } else if (strategyData.allocated < targetAllocation) { // Deposit uint256 targetCash = totalAssetsAllocatableCache * strategies[address(0)].allocationPoints / totalAllocationPointsCache; @@ -673,7 +673,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn // Calculate available cash to put in strategies uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; - amountToRebalance = targetAllocation - currentAllocation; + amountToRebalance = targetAllocation - strategyData.allocated; if (amountToRebalance > cashAvailable) { amountToRebalance = cashAvailable; } @@ -690,11 +690,11 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn // Do required approval (safely) and deposit IERC20(asset()).safeApprove(_strategy, amountToRebalance); IERC4626(_strategy).deposit(amountToRebalance, address(this)); - strategies[_strategy].allocated = uint120(currentAllocation + amountToRebalance); + strategies[_strategy].allocated = uint120(strategyData.allocated + amountToRebalance); totalAllocated += amountToRebalance; } - emit Rebalance(_strategy, currentAllocation, targetAllocation, amountToRebalance); + emit Rebalance(_strategy, strategyData.allocated, targetAllocation, amountToRebalance); } function _harvest(address _strategy) internal { diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol new file mode 100644 index 00000000..86d7d6d6 --- /dev/null +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + FourSixTwoSixAggBase, + FourSixTwoSixAgg, + console2, + EVault, + IEVault, + IRMTestDefault, + TestERC20 +} from "../common/FourSixTwoSixAggBase.t.sol"; + +contract StrategyCapE2ETest is FourSixTwoSixAggBase { + uint256 user1InitialBalance = 100000e18; + + function setUp() public virtual override { + super.setUp(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + + assetTST.mint(user1, user1InitialBalance); + } + + function testSetCap() public { + uint256 cap = 1000000e18; + + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).cap, 0); + + vm.prank(manager); + fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); + + FourSixTwoSixAgg.Strategy memory strategy = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(strategy.cap, cap); + } + + function testSetCapForInactiveStrategy() public { + uint256 cap = 1000000e18; + + vm.prank(manager); + vm.expectRevert(FourSixTwoSixAgg.InactiveStrategy.selector); + fourSixTwoSixAgg.setStrategyCap(address(0x2), cap); + } +} From 2877e6821b4720ec0b08de6bf5f1f62e18ff4e0c Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:23:55 +0300 Subject: [PATCH 133/316] test: more tests --- src/FourSixTwoSixAgg.sol | 5 +- test/e2e/StrategyCapE2ETest.t.sol | 105 ++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 1b9bc81f..569681bb 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -107,6 +107,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn event AddStrategy(address indexed strategy, uint256 allocationPoints); event RemoveStrategy(address indexed _strategy); event AccruePerformanceFee(address indexed feeRecipient, uint256 performanceFee, uint256 yield, uint256 feeShares); + event SetStrategyCap(address indexed strategy, uint256 cap); /// @notice Modifier to require an account status check on the EVC. /// @dev Calls `requireAccountStatusCheck` function from EVC for the specified account after the function body. @@ -310,8 +311,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn emit AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); } - event SetStrategyCap(address indexed strategy, uint256 cap); - function setStrategyCap(address _strategy, uint256 _cap) external nonReentrant onlyRole(STRATEGY_MANAGER_ROLE) { Strategy memory strategyDataCache = strategies[_strategy]; @@ -642,7 +641,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn Strategy memory strategyData = strategies[_strategy]; // no rebalance if strategy have an allocated amount greater than cap - if ((strategyData.cap > 0) && (strategyData.allocated > strategyData.cap)) return; + if ((strategyData.cap > 0) && (strategyData.allocated >= strategyData.cap)) return; uint256 totalAllocationPointsCache = totalAllocationPoints; uint256 totalAssetsAllocatableCache = totalAssetsAllocatable(); diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index 86d7d6d6..a364f8d0 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -43,4 +43,109 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { vm.expectRevert(FourSixTwoSixAgg.InactiveStrategy.selector); fourSixTwoSixAgg.setStrategyCap(address(0x2), cap); } + + function testRebalanceAfterHittingCap() public { + uint256 cap = 3333333333333333333333; + vm.prank(manager); + fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); + + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq( + (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash + ); + } + + // deposit and rebalance again, no rebalance should happen as strategy reached max cap + vm.warp(block.timestamp + 86400); + vm.startPrank(user1); + + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + + uint256 strategyAllocatedBefore = (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated; + + fourSixTwoSixAgg.rebalance(address(eTST)); + vm.stopPrank(); + + assertEq(strategyAllocatedBefore, (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated); + } + + function testRebalanceWhentargetAllocationGreaterThanCap() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + // set cap 10% less than target allocation + uint256 cap = expectedStrategyCash * 9e17 / 1e18; + vm.prank(manager); + fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); + + vm.prank(user1); + fourSixTwoSixAgg.rebalance(address(eTST)); + + assertEq(fourSixTwoSixAgg.totalAllocated(), cap); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), cap); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated + cap); + } + } } From 6e8192a8805d4c894ed05216059f27a2a2b088f2 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:26:25 +0300 Subject: [PATCH 134/316] chore: natspec --- src/FourSixTwoSixAgg.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 569681bb..d89c83b2 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -311,6 +311,10 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn emit AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); } + /// @notice Set cap on strategy allocated amount. + /// @dev By default, cap is set to 0, not activated. + /// @param _strategy Strategy address. + /// @param _cap Cap amount function setStrategyCap(address _strategy, uint256 _cap) external nonReentrant onlyRole(STRATEGY_MANAGER_ROLE) { Strategy memory strategyDataCache = strategies[_strategy]; From fe7d60462017e8db8084f7b5647579284d5ea55f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:11:20 +0300 Subject: [PATCH 135/316] feat: init hooks --- src/Hooks.sol | 40 +++++++++++++++++++++++++++++++++++ src/interface/IHookTarget.sol | 14 ++++++++++++ src/lib/HooksLib.sol | 24 +++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 src/Hooks.sol create mode 100644 src/interface/IHookTarget.sol create mode 100644 src/lib/HooksLib.sol diff --git a/src/Hooks.sol b/src/Hooks.sol new file mode 100644 index 00000000..29571f2c --- /dev/null +++ b/src/Hooks.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {HooksLib, HooksType} from "./lib/HooksLib.sol"; +import {IHookTarget} from "./interface/IHookTarget.sol"; + +contract Hooks { + using HooksLib for HooksType; + + error NotHooksContract(); + error InvalidHookedFns(); + + uint32 constant public DEPOSIT = 1 << 0; + uint32 constant public MINT = 1 << 1; + uint32 constant public WITHDRAW = 1 << 2; + uint32 constant public REDEEM = 1 << 3; + uint32 constant public REBALANCE = 1 << 4; + uint32 constant public HARVEST = 1 << 5; + uint32 constant public GULP = 1 << 6; + uint32 constant public REORDER_WITHDRAWAL_QUEUE = 1 << 7; + uint32 constant public ADD_STRATEGY = 1 << 8; + uint32 constant public REMOVE_STRATEGY = 1 << 9; + + uint32 constant ACTIONS_COUNTER = 1 << 10; + + address public hooksContract; + HooksType public hookedFn; + + function setHookConfig(address _hooksContract, uint32 _hookedFns) external { + if ( + _hooksContract != address(0) + && IHookTarget(_hooksContract).isHookTarget() != IHookTarget.isHookTarget.selector + ) revert NotHooksContract(); + + if (_hookedFns >= ACTIONS_COUNTER) revert InvalidHookedFns(); + + hooksContract = _hooksContract; + hookedFn = HooksType.wrap(_hookedFns); + } +} \ No newline at end of file diff --git a/src/interface/IHookTarget.sol b/src/interface/IHookTarget.sol new file mode 100644 index 00000000..23889c2b --- /dev/null +++ b/src/interface/IHookTarget.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity >=0.8.0; + +/// @title IHookTarget +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @custom:security-contact security@euler.xyz +/// @notice Provides an interface for the hook target contract +interface IHookTarget { + /// @notice If given contract is a hook target, it is expected to return the bytes4 magic value that is the selector + /// of this function + /// @return The bytes4 magic value (0x87439e04) that is the selector of this function + function isHookTarget() external returns (bytes4); +} \ No newline at end of file diff --git a/src/lib/HooksLib.sol b/src/lib/HooksLib.sol new file mode 100644 index 00000000..70ca221b --- /dev/null +++ b/src/lib/HooksLib.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +/// @title HooksLib +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Library for `Hooks` custom type +library HooksLib { + /// @dev Are *all* of the Hooks in bitMask set? + function isSet(HooksType self, uint32 bitMask) internal pure returns (bool) { + return (HooksType.unwrap(self) & bitMask) == bitMask; + } + + /// @dev Are *none* of the Hooks in bitMask set? + function isNotSet(HooksType self, uint32 bitMask) internal pure returns (bool) { + return (HooksType.unwrap(self) & bitMask) == 0; + } + + function toUint32(HooksType self) internal pure returns (uint32) { + return HooksType.unwrap(self); + } +} + +type HooksType is uint32; \ No newline at end of file From c1fde1ef2bdba3f709d90e909f146cb3f94fcf77 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:12:06 +0300 Subject: [PATCH 136/316] chore: lint --- src/Hooks.sol | 22 +++++++++++----------- src/interface/IHookTarget.sol | 2 +- src/lib/HooksLib.sol | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Hooks.sol b/src/Hooks.sol index 29571f2c..de55e9b4 100644 --- a/src/Hooks.sol +++ b/src/Hooks.sol @@ -10,16 +10,16 @@ contract Hooks { error NotHooksContract(); error InvalidHookedFns(); - uint32 constant public DEPOSIT = 1 << 0; - uint32 constant public MINT = 1 << 1; - uint32 constant public WITHDRAW = 1 << 2; - uint32 constant public REDEEM = 1 << 3; - uint32 constant public REBALANCE = 1 << 4; - uint32 constant public HARVEST = 1 << 5; - uint32 constant public GULP = 1 << 6; - uint32 constant public REORDER_WITHDRAWAL_QUEUE = 1 << 7; - uint32 constant public ADD_STRATEGY = 1 << 8; - uint32 constant public REMOVE_STRATEGY = 1 << 9; + uint32 public constant DEPOSIT = 1 << 0; + uint32 public constant MINT = 1 << 1; + uint32 public constant WITHDRAW = 1 << 2; + uint32 public constant REDEEM = 1 << 3; + uint32 public constant REBALANCE = 1 << 4; + uint32 public constant HARVEST = 1 << 5; + uint32 public constant GULP = 1 << 6; + uint32 public constant REORDER_WITHDRAWAL_QUEUE = 1 << 7; + uint32 public constant ADD_STRATEGY = 1 << 8; + uint32 public constant REMOVE_STRATEGY = 1 << 9; uint32 constant ACTIONS_COUNTER = 1 << 10; @@ -37,4 +37,4 @@ contract Hooks { hooksContract = _hooksContract; hookedFn = HooksType.wrap(_hookedFns); } -} \ No newline at end of file +} diff --git a/src/interface/IHookTarget.sol b/src/interface/IHookTarget.sol index 23889c2b..45ef3ac3 100644 --- a/src/interface/IHookTarget.sol +++ b/src/interface/IHookTarget.sol @@ -11,4 +11,4 @@ interface IHookTarget { /// of this function /// @return The bytes4 magic value (0x87439e04) that is the selector of this function function isHookTarget() external returns (bytes4); -} \ No newline at end of file +} diff --git a/src/lib/HooksLib.sol b/src/lib/HooksLib.sol index 70ca221b..7de1f6bb 100644 --- a/src/lib/HooksLib.sol +++ b/src/lib/HooksLib.sol @@ -21,4 +21,4 @@ library HooksLib { } } -type HooksType is uint32; \ No newline at end of file +type HooksType is uint32; From 68457f0eefe1619d32e0ff37fa382acd956b9629 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 10 Jun 2024 18:45:05 +0300 Subject: [PATCH 137/316] feat: hooks implementation --- src/FourSixTwoSixAgg.sol | 33 ++++++++++---- src/Hooks.sol | 60 +++++++++++++++++--------- test/common/FourSixTwoSixAggBase.t.sol | 11 +++-- test/e2e/BalanceForwarderE2ETest.t.sol | 4 +- 4 files changed, 71 insertions(+), 37 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index d89c83b2..4873b789 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -10,12 +10,13 @@ import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; import {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol"; +import {Hooks} from "./Hooks.sol"; /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol /// @dev inspired by Yearn v3 ❤️ -contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEnumerable { +contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEnumerable, Hooks { using SafeERC20 for IERC20; using SafeCast for uint256; @@ -48,8 +49,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn bytes32 public constant STRATEGY_ADDER_ROLE_ADMINROLE = keccak256("STRATEGY_ADDER_ROLE_ADMINROLE"); bytes32 public constant STRATEGY_REMOVER_ROLE = keccak256("STRATEGY_REMOVER_ROLE"); bytes32 public constant STRATEGY_REMOVER_ROLE_ADMINROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMINROLE"); - bytes32 public constant TREASURY_MANAGER_ROLE = keccak256("TREASURY_MANAGER_ROLE"); - bytes32 public constant TREASURY_MANAGER_ROLE_ADMINROLE = keccak256("TREASURY_MANAGER_ROLE_ADMINROLE"); + bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); + bytes32 public constant MANAGER_ROLE_ADMINROLE = keccak256("MANAGER_ROLE_ADMINROLE"); /// @dev The maximum performanceFee the vault can have is 50% uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; @@ -179,12 +180,12 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _setRoleAdmin(WITHDRAW_QUEUE_MANAGER_ROLE, WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE); _setRoleAdmin(STRATEGY_ADDER_ROLE, STRATEGY_ADDER_ROLE_ADMINROLE); _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMINROLE); - _setRoleAdmin(TREASURY_MANAGER_ROLE, TREASURY_MANAGER_ROLE_ADMINROLE); + _setRoleAdmin(MANAGER_ROLE, MANAGER_ROLE_ADMINROLE); } /// @notice Set performance fee recipient address /// @notice @param _newFeeRecipient Recipient address - function setFeeRecipient(address _newFeeRecipient) external onlyRole(TREASURY_MANAGER_ROLE) { + function setFeeRecipient(address _newFeeRecipient) external onlyRole(MANAGER_ROLE) { if (_newFeeRecipient == feeRecipient) revert FeeRecipientAlreadySet(); emit SetFeeRecipient(feeRecipient, _newFeeRecipient); @@ -194,7 +195,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @notice Set performance fee (1e18 == 100%) /// @notice @param _newFee Fee rate - function setPerformanceFee(uint256 _newFee) external onlyRole(TREASURY_MANAGER_ROLE) { + function setPerformanceFee(uint256 _newFee) external onlyRole(MANAGER_ROLE) { if (_newFee > MAX_PERFORMANCE_FEE) revert MaxPerformanceFeeExceeded(); if (feeRecipient == address(0)) revert FeeRecipientNotSet(); if (_newFee == performanceFee) revert PerformanceFeeAlreadySet(); @@ -206,7 +207,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @notice Opt in to strategy rewards /// @param _strategy Strategy address - function optInStrategyRewards(address _strategy) external onlyRole(TREASURY_MANAGER_ROLE) { + function optInStrategyRewards(address _strategy) external onlyRole(MANAGER_ROLE) { if (!strategies[_strategy].active) revert InactiveStrategy(); IBalanceForwarder(_strategy).enableBalanceForwarder(); @@ -216,7 +217,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @notice Opt out of strategy rewards /// @param _strategy Strategy address - function optOutStrategyRewards(address _strategy) external onlyRole(TREASURY_MANAGER_ROLE) { + function optOutStrategyRewards(address _strategy) external onlyRole(MANAGER_ROLE) { IBalanceForwarder(_strategy).disableBalanceForwarder(); emit OptOutStrategyRewards(_strategy); @@ -234,7 +235,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn address _reward, address _recipient, bool _forfeitRecentReward - ) external onlyRole(TREASURY_MANAGER_ROLE) { + ) external onlyRole(MANAGER_ROLE) { address rewardStreams = IBalanceForwarder(_strategy).balanceTrackerAddress(); IRewardStreams(rewardStreams).claimReward(_rewarded, _reward, _recipient, _forfeitRecentReward); @@ -367,6 +368,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert StrategyAlreadyExist(); } + _callHook(ADD_STRATEGY, _msgSender()); + strategies[_strategy] = Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); @@ -389,6 +392,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert AlreadyRemoved(); } + _callHook(REMOVE_STRATEGY, _msgSender()); + totalAllocationPoints -= strategyStorage.allocationPoints; strategyStorage.active = false; strategyStorage.allocationPoints = 0; @@ -513,6 +518,10 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn return super.redeem(shares, receiver, owner); } + function setHooksConfig(address _hooksTarget, uint32 _hookedFns) public override onlyRole(MANAGER_ROLE) { + super.setHooksConfig(_hooksTarget, _hookedFns); + } + /// @notice update accrued interest. /// @return struct ESRSlot struct. function _updateInterestAccrued() internal returns (ESRSlot memory) { @@ -545,6 +554,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @dev Increate the total assets deposited, and call IERC4626._deposit() /// @dev See {IERC4626-_deposit}. function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { + _callHook(DEPOSIT, caller); + totalAssetsDeposited += assets; super._deposit(caller, receiver, assets, shares); @@ -558,6 +569,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn internal override { + _callHook(WITHDRAW, caller); + totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); @@ -640,6 +653,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn return; //nothing to rebalance as this is the cash reserve } + _callHook(REBALANCE, _msgSender()); + _harvest(_strategy); Strategy memory strategyData = strategies[_strategy]; diff --git a/src/Hooks.sol b/src/Hooks.sol index de55e9b4..5e32dc86 100644 --- a/src/Hooks.sol +++ b/src/Hooks.sol @@ -4,37 +4,57 @@ pragma solidity ^0.8.0; import {HooksLib, HooksType} from "./lib/HooksLib.sol"; import {IHookTarget} from "./interface/IHookTarget.sol"; -contract Hooks { +abstract contract Hooks { using HooksLib for HooksType; error NotHooksContract(); error InvalidHookedFns(); + error EmptyError(); uint32 public constant DEPOSIT = 1 << 0; - uint32 public constant MINT = 1 << 1; - uint32 public constant WITHDRAW = 1 << 2; - uint32 public constant REDEEM = 1 << 3; - uint32 public constant REBALANCE = 1 << 4; - uint32 public constant HARVEST = 1 << 5; - uint32 public constant GULP = 1 << 6; - uint32 public constant REORDER_WITHDRAWAL_QUEUE = 1 << 7; - uint32 public constant ADD_STRATEGY = 1 << 8; - uint32 public constant REMOVE_STRATEGY = 1 << 9; - - uint32 constant ACTIONS_COUNTER = 1 << 10; - - address public hooksContract; + uint32 public constant WITHDRAW = 1 << 1; + uint32 public constant REBALANCE = 1 << 2; + uint32 public constant ADD_STRATEGY = 1 << 3; + uint32 public constant REMOVE_STRATEGY = 1 << 4; + + uint32 constant ACTIONS_COUNTER = 1 << 5; + + address public hooksTarget; HooksType public hookedFn; - function setHookConfig(address _hooksContract, uint32 _hookedFns) external { - if ( - _hooksContract != address(0) - && IHookTarget(_hooksContract).isHookTarget() != IHookTarget.isHookTarget.selector - ) revert NotHooksContract(); + function setHooksConfig(address _hooksTarget, uint32 _hookedFns) public virtual { + if (_hooksTarget != address(0) && IHookTarget(_hooksTarget).isHookTarget() != IHookTarget.isHookTarget.selector) + { + revert NotHooksContract(); + } if (_hookedFns >= ACTIONS_COUNTER) revert InvalidHookedFns(); - hooksContract = _hooksContract; + hooksTarget = _hooksTarget; hookedFn = HooksType.wrap(_hookedFns); } + + // Checks whether a hook has been installed for the _operation and if so, invokes the hook target. + // If the hook target is zero address, this will revert. + function _callHook(uint32 _operation, address _caller) internal { + if (hookedFn.isNotSet(_operation)) return; + + address target = hooksTarget; + + // if (hookTarget == address(0)) revert E_OperationDisabled(); + + (bool success, bytes memory data) = target.call(abi.encodePacked(msg.data, _caller)); + + if (!success) _revertBytes(data); + } + + function _revertBytes(bytes memory _errorMsg) private pure { + if (_errorMsg.length > 0) { + assembly { + revert(add(32, _errorMsg), mload(_errorMsg)) + } + } + + revert EmptyError(); + } } diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 5c2dc191..66c554ae 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -38,14 +38,14 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE(), deployer); // grant roles to manager fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE(), manager); vm.stopPrank(); } @@ -74,21 +74,20 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE() ); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE()), - fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMINROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.MANAGER_ROLE()), fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE() ); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMINROLE(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER_ROLE(), manager)); } function _addStrategy(address from, address strategy, uint256 allocationPoints) internal { diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 33927d78..b327e1b4 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -39,14 +39,14 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE(), deployer); // grant roles to manager fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.TREASURY_MANAGER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; From 981f2a2b46d64366f03c70751addc6d48a710712 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 10 Jun 2024 19:14:24 +0300 Subject: [PATCH 138/316] chore: clean & rename --- src/FourSixTwoSixAgg.sol | 14 ++++++------ src/Hooks.sol | 30 +++++++++++++++----------- src/interface/IHookTarget.sol | 14 ------------ test/common/FourSixTwoSixAggBase.t.sol | 2 ++ 4 files changed, 27 insertions(+), 33 deletions(-) delete mode 100644 src/interface/IHookTarget.sol diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 4873b789..0e30ee8a 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -368,7 +368,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert StrategyAlreadyExist(); } - _callHook(ADD_STRATEGY, _msgSender()); + _callHookTarget(ADD_STRATEGY, _msgSender()); strategies[_strategy] = Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); @@ -392,7 +392,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn revert AlreadyRemoved(); } - _callHook(REMOVE_STRATEGY, _msgSender()); + _callHookTarget(REMOVE_STRATEGY, _msgSender()); totalAllocationPoints -= strategyStorage.allocationPoints; strategyStorage.active = false; @@ -518,8 +518,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn return super.redeem(shares, receiver, owner); } - function setHooksConfig(address _hooksTarget, uint32 _hookedFns) public override onlyRole(MANAGER_ROLE) { - super.setHooksConfig(_hooksTarget, _hookedFns); + function setHooksConfig(address _hookTarget, uint32 _hookedFns) public override onlyRole(MANAGER_ROLE) { + super.setHooksConfig(_hookTarget, _hookedFns); } /// @notice update accrued interest. @@ -554,7 +554,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn /// @dev Increate the total assets deposited, and call IERC4626._deposit() /// @dev See {IERC4626-_deposit}. function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { - _callHook(DEPOSIT, caller); + _callHookTarget(DEPOSIT, caller); totalAssetsDeposited += assets; @@ -569,7 +569,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn internal override { - _callHook(WITHDRAW, caller); + _callHookTarget(WITHDRAW, caller); totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); @@ -653,7 +653,7 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn return; //nothing to rebalance as this is the cash reserve } - _callHook(REBALANCE, _msgSender()); + _callHookTarget(REBALANCE, _msgSender()); _harvest(_strategy); diff --git a/src/Hooks.sol b/src/Hooks.sol index 5e32dc86..e044e754 100644 --- a/src/Hooks.sol +++ b/src/Hooks.sol @@ -2,11 +2,12 @@ pragma solidity ^0.8.0; import {HooksLib, HooksType} from "./lib/HooksLib.sol"; -import {IHookTarget} from "./interface/IHookTarget.sol"; +import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; abstract contract Hooks { using HooksLib for HooksType; + error InvalidHooksTarget(); error NotHooksContract(); error InvalidHookedFns(); error EmptyError(); @@ -19,27 +20,32 @@ abstract contract Hooks { uint32 constant ACTIONS_COUNTER = 1 << 5; - address public hooksTarget; - HooksType public hookedFn; + address public hookTarget; + HooksType public hookedFns; - function setHooksConfig(address _hooksTarget, uint32 _hookedFns) public virtual { - if (_hooksTarget != address(0) && IHookTarget(_hooksTarget).isHookTarget() != IHookTarget.isHookTarget.selector) - { + function getHooksConfig() external view returns (address, uint32) { + return (hookTarget, hookedFns.toUint32()); + } + + function setHooksConfig(address _hookTarget, uint32 _hookedFns) public virtual { + if (_hookTarget != address(0) && IHookTarget(_hookTarget).isHookTarget() != IHookTarget.isHookTarget.selector) { revert NotHooksContract(); } - + if (_hookedFns != 0 && _hookTarget == address(0)) { + revert InvalidHooksTarget(); + } if (_hookedFns >= ACTIONS_COUNTER) revert InvalidHookedFns(); - hooksTarget = _hooksTarget; - hookedFn = HooksType.wrap(_hookedFns); + hookTarget = _hookTarget; + hookedFns = HooksType.wrap(_hookedFns); } // Checks whether a hook has been installed for the _operation and if so, invokes the hook target. // If the hook target is zero address, this will revert. - function _callHook(uint32 _operation, address _caller) internal { - if (hookedFn.isNotSet(_operation)) return; + function _callHookTarget(uint32 _operation, address _caller) internal { + if (hookedFns.isNotSet(_operation)) return; - address target = hooksTarget; + address target = hookTarget; // if (hookTarget == address(0)) revert E_OperationDisabled(); diff --git a/src/interface/IHookTarget.sol b/src/interface/IHookTarget.sol deleted file mode 100644 index 45ef3ac3..00000000 --- a/src/interface/IHookTarget.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -pragma solidity >=0.8.0; - -/// @title IHookTarget -/// @author Euler Labs (https://www.eulerlabs.com/) -/// @custom:security-contact security@euler.xyz -/// @notice Provides an interface for the hook target contract -interface IHookTarget { - /// @notice If given contract is a hook target, it is expected to return the bytes4 magic value that is the selector - /// of this function - /// @return The bytes4 magic value (0x87439e04) that is the selector of this function - function isHookTarget() external returns (bytes4); -} diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 66c554ae..b605561c 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.0; import "evk/test/unit/evault/EVaultTestBase.t.sol"; import {FourSixTwoSixAgg} from "../../src/FourSixTwoSixAgg.sol"; +import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; +import {Hooks} from "../../src/Hooks.sol"; contract FourSixTwoSixAggBase is EVaultTestBase { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; From a09c8a365244259bac00253549644923402645e1 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 10 Jun 2024 19:14:33 +0300 Subject: [PATCH 139/316] test: init tests --- test/e2e/HooksE2ETest.t.sol | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 test/e2e/HooksE2ETest.t.sol diff --git a/test/e2e/HooksE2ETest.t.sol b/test/e2e/HooksE2ETest.t.sol new file mode 100644 index 00000000..a41e826c --- /dev/null +++ b/test/e2e/HooksE2ETest.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + FourSixTwoSixAggBase, + FourSixTwoSixAgg, + console2, + EVault, + IEVault, + IRMTestDefault, + TestERC20, + IHookTarget, + Hooks +} from "../common/FourSixTwoSixAggBase.t.sol"; + +contract HooksE2ETest is FourSixTwoSixAggBase { + uint256 user1InitialBalance = 100000e18; + + function setUp() public virtual override { + super.setUp(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + + assetTST.mint(user1, user1InitialBalance); + } + + function testSetHooksConfig() public { + uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT() | fourSixTwoSixAgg.WITHDRAW() + | fourSixTwoSixAgg.REBALANCE() | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); + + vm.startPrank(manager); + address hooksContract = address(new HooksContract()); + fourSixTwoSixAgg.setHooksConfig(hooksContract, expectedHookedFns); + vm.stopPrank(); + + (address hookTarget, uint32 hookedFns) = fourSixTwoSixAgg.getHooksConfig(); + + assertEq(hookTarget, hooksContract); + assertEq(hookedFns, expectedHookedFns); + } + + function testSetHooksConfigWithAddressZero() public { + uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT() | fourSixTwoSixAgg.WITHDRAW() + | fourSixTwoSixAgg.REBALANCE() | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); + + vm.startPrank(manager); + vm.expectRevert(Hooks.InvalidHooksTarget.selector); + fourSixTwoSixAgg.setHooksConfig(address(0), expectedHookedFns); + vm.stopPrank(); + } +} + +contract HooksContract is IHookTarget { + function isHookTarget() external returns (bytes4) { + return this.isHookTarget.selector; + } +} From 43455a01eeb06d9316199e0010a53a4112fcbf89 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:18:32 +0300 Subject: [PATCH 140/316] tests --- src/Hooks.sol | 2 -- test/e2e/HooksE2ETest.t.sol | 58 ++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/Hooks.sol b/src/Hooks.sol index e044e754..c01e974c 100644 --- a/src/Hooks.sol +++ b/src/Hooks.sol @@ -47,8 +47,6 @@ abstract contract Hooks { address target = hookTarget; - // if (hookTarget == address(0)) revert E_OperationDisabled(); - (bool success, bytes memory data) = target.call(abi.encodePacked(msg.data, _caller)); if (!success) _revertBytes(data); diff --git a/test/e2e/HooksE2ETest.t.sol b/test/e2e/HooksE2ETest.t.sol index a41e826c..62454ad6 100644 --- a/test/e2e/HooksE2ETest.t.sol +++ b/test/e2e/HooksE2ETest.t.sol @@ -49,10 +49,66 @@ contract HooksE2ETest is FourSixTwoSixAggBase { fourSixTwoSixAgg.setHooksConfig(address(0), expectedHookedFns); vm.stopPrank(); } + + function testSetHooksConfigWithNotHooksContract() public { + uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT() | fourSixTwoSixAgg.WITHDRAW() + | fourSixTwoSixAgg.REBALANCE() | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); + + vm.startPrank(manager); + address hooksContract = address(new NotHooksContract()); + vm.expectRevert(Hooks.NotHooksContract.selector); + fourSixTwoSixAgg.setHooksConfig(hooksContract, expectedHookedFns); + vm.stopPrank(); + } + + function testSetHooksConfigWithInvalidHookedFns() public { + uint32 expectedHookedFns = 1 << 5; + vm.startPrank(manager); + address hooksContract = address(new HooksContract()); + vm.expectRevert(Hooks.InvalidHookedFns.selector); + fourSixTwoSixAgg.setHooksConfig(hooksContract, expectedHookedFns); + vm.stopPrank(); + } + + function testHookedDeposit() public { + uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT(); + vm.startPrank(manager); + address hooksContract = address(new HooksContract()); + fourSixTwoSixAgg.setHooksConfig(hooksContract, expectedHookedFns); + vm.stopPrank(); + + uint256 amountToDeposit = 10000e18; + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + } } contract HooksContract is IHookTarget { - function isHookTarget() external returns (bytes4) { + function isHookTarget() external pure returns (bytes4) { return this.isHookTarget.selector; } + + fallback() external payable { + } } + +contract NotHooksContract is IHookTarget { + function isHookTarget() external pure returns (bytes4) { + return 0x0; + } +} \ No newline at end of file From 3b0fa7f232cb1cb7bb665784ad3fba9013e06e43 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:42:47 +0300 Subject: [PATCH 141/316] chore: natspec --- src/FourSixTwoSixAgg.sol | 4 ++++ src/Hooks.sol | 20 ++++++++++++++++---- test/e2e/HooksE2ETest.t.sol | 7 +++---- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 0e30ee8a..000b4307 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -518,6 +518,10 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn return super.redeem(shares, receiver, owner); } + /// @notice Set hooks contract and hooked functions. + /// @dev This funtion should be overriden to implement access control. + /// @param _hookTarget Hooks contract. + /// @param _hookedFns Hooked functions. function setHooksConfig(address _hookTarget, uint32 _hookedFns) public override onlyRole(MANAGER_ROLE) { super.setHooksConfig(_hookTarget, _hookedFns); } diff --git a/src/Hooks.sol b/src/Hooks.sol index c01e974c..17206686 100644 --- a/src/Hooks.sol +++ b/src/Hooks.sol @@ -20,13 +20,22 @@ abstract contract Hooks { uint32 constant ACTIONS_COUNTER = 1 << 5; + /// @dev Contract with hooks implementation address public hookTarget; + /// @dev Hooked functions HooksType public hookedFns; + /// @notice Get the hooks contract and the hooked functions. + /// @return address Hooks contract. + /// @return uint32 Hooked functions. function getHooksConfig() external view returns (address, uint32) { return (hookTarget, hookedFns.toUint32()); } + /// @notice Set hooks contract and hooked functions. + /// @dev This funtion should be overriden to implement access control. + /// @param _hookTarget Hooks contract. + /// @param _hookedFns Hooked functions. function setHooksConfig(address _hookTarget, uint32 _hookedFns) public virtual { if (_hookTarget != address(0) && IHookTarget(_hookTarget).isHookTarget() != IHookTarget.isHookTarget.selector) { revert NotHooksContract(); @@ -40,10 +49,11 @@ abstract contract Hooks { hookedFns = HooksType.wrap(_hookedFns); } - // Checks whether a hook has been installed for the _operation and if so, invokes the hook target. - // If the hook target is zero address, this will revert. - function _callHookTarget(uint32 _operation, address _caller) internal { - if (hookedFns.isNotSet(_operation)) return; + /// @notice Checks whether a hook has been installed for the function and if so, invokes the hook target. + /// @param _fn Function to check hook for. + /// @param _caller Caller's address. + function _callHookTarget(uint32 _fn, address _caller) internal { + if (hookedFns.isNotSet(_fn)) return; address target = hookTarget; @@ -52,6 +62,8 @@ abstract contract Hooks { if (!success) _revertBytes(data); } + /// @dev Revert with call error or EmptyError + /// @param _errorMsg call revert message function _revertBytes(bytes memory _errorMsg) private pure { if (_errorMsg.length > 0) { assembly { diff --git a/test/e2e/HooksE2ETest.t.sol b/test/e2e/HooksE2ETest.t.sol index 62454ad6..62176738 100644 --- a/test/e2e/HooksE2ETest.t.sol +++ b/test/e2e/HooksE2ETest.t.sol @@ -60,7 +60,7 @@ contract HooksE2ETest is FourSixTwoSixAggBase { fourSixTwoSixAgg.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); } - + function testSetHooksConfigWithInvalidHookedFns() public { uint32 expectedHookedFns = 1 << 5; vm.startPrank(manager); @@ -103,12 +103,11 @@ contract HooksContract is IHookTarget { return this.isHookTarget.selector; } - fallback() external payable { - } + fallback() external payable {} } contract NotHooksContract is IHookTarget { function isHookTarget() external pure returns (bytes4) { return 0x0; } -} \ No newline at end of file +} From 3bab3fac1e050ea00427aad34b16e57a04c98e23 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:45:21 +0300 Subject: [PATCH 142/316] chore: add reference --- src/lib/HooksLib.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/HooksLib.sol b/src/lib/HooksLib.sol index 7de1f6bb..c424ef02 100644 --- a/src/lib/HooksLib.sol +++ b/src/lib/HooksLib.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.0; /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) /// @notice Library for `Hooks` custom type +/// @dev This is copied from https://github.com/euler-xyz/euler-vault-kit/blob/30b0b9e36b0a912fe430c7482e9b3bb12d180a4e/src/EVault/shared/types/Flags.sol library HooksLib { /// @dev Are *all* of the Hooks in bitMask set? function isSet(HooksType self, uint32 bitMask) internal pure returns (bool) { From c8ab40bf1c6845dc84e0579046bb30a3017c91f7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:35:28 +0300 Subject: [PATCH 143/316] add Rebalancer.sol --- src/FourSixTwoSixAgg.sol | 133 ++++-------------- src/Rebalancer.sol | 90 ++++++++++++ src/interface/IFourSixTwoSixAgg.sol | 23 +++ test/common/FourSixTwoSixAggBase.t.sol | 7 + ...positRebalanceHarvestWithdrawE2ETest.t.sol | 12 +- test/e2e/PerformanceFeeE2ETest.t.sol | 2 +- test/e2e/StrategyCapE2ETest.t.sol | 6 +- test/unit/GulpTest.t.sol | 2 +- test/unit/HarvestTest.t.sol | 2 +- test/unit/RebalanceTest.t.sol | 18 +-- 10 files changed, 169 insertions(+), 126 deletions(-) create mode 100644 src/Rebalancer.sol create mode 100644 src/interface/IFourSixTwoSixAgg.sol diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 000b4307..f48ae71c 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +// external dep +// internal dep +import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {Context} from "@openzeppelin/utils/Context.sol"; import {ERC20, IERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; @@ -16,7 +19,7 @@ import {Hooks} from "./Hooks.sol"; /// @dev Do NOT use with rebasing tokens /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol /// @dev inspired by Yearn v3 ❤️ -contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEnumerable, Hooks { +contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC4626, AccessControlEnumerable, Hooks { using SafeERC20 for IERC20; using SafeCast for uint256; @@ -51,6 +54,8 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn bytes32 public constant STRATEGY_REMOVER_ROLE_ADMINROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMINROLE"); bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); bytes32 public constant MANAGER_ROLE_ADMINROLE = keccak256("MANAGER_ROLE_ADMINROLE"); + bytes32 public constant REBALANCER_ROLE = keccak256("REBALANCER_ROLE"); + bytes32 public constant REBALANCER_ROLE_ADMINROLE = keccak256("REBALANCER_ROLE_ADMINROLE"); /// @dev The maximum performanceFee the vault can have is 50% uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; @@ -82,18 +87,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn uint8 locked; } - /// @dev A struct that hold a strategy allocation's config - /// allocated: amount of asset deposited into strategy - /// allocationPoints: number of points allocated to this strategy - /// active: a boolean to indice if this strategy is active or not - /// cap: an optional cap in terms of deposited underlying asset. By default, it is set to 0(not activated) - struct Strategy { - uint120 allocated; - uint120 allocationPoints; - bool active; - uint120 cap; - } - event SetFeeRecipient(address indexed oldRecipient, address indexed newRecipient); event SetPerformanceFee(uint256 oldFee, uint256 newFee); event OptInStrategyRewards(address indexed strategy); @@ -256,24 +249,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _disableBalanceForwarder(_msgSender()); } - /// @notice Rebalance strategy's allocation. - /// @param _strategy Address of strategy to rebalance. - function rebalance(address _strategy) external nonReentrant { - _rebalance(_strategy); - - _gulp(); - } - - /// @notice Rebalance multiple strategies. - /// @param _strategies An array of strategy addresses to rebalance. - function rebalanceMultipleStrategies(address[] calldata _strategies) external nonReentrant { - for (uint256 i; i < _strategies.length; ++i) { - _rebalance(_strategies[i]); - } - - _gulp(); - } - /// @notice Harvest strategy. /// @param strategy address of strategy function harvest(address strategy) external nonReentrant { @@ -291,6 +266,28 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn _gulp(); } + function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) + external + nonReentrant + onlyRole(REBALANCER_ROLE) + { + _harvest(_strategy); + + Strategy memory strategyData = strategies[_strategy]; + + if (_isDeposit) { + // Do required approval (safely) and deposit + IERC20(asset()).safeApprove(_strategy, _amountToRebalance); + IERC4626(_strategy).deposit(_amountToRebalance, address(this)); + strategies[_strategy].allocated = uint120(strategyData.allocated + _amountToRebalance); + totalAllocated += _amountToRebalance; + } else { + IERC4626(_strategy).withdraw(_amountToRebalance, address(this), address(this)); + strategies[_strategy].allocated = (strategyData.allocated - _amountToRebalance).toUint120(); + totalAllocated -= _amountToRebalance; + } + } + /// @notice Adjust a certain strategy's allocation points. /// @dev Can only be called by an address that have the STRATEGY_MANAGER_ROLE /// @param _strategy address of strategy @@ -645,80 +642,6 @@ contract FourSixTwoSixAgg is BalanceForwarder, EVCUtil, ERC4626, AccessControlEn emit Gulp(esrSlotCache.interestLeft, esrSlotCache.interestSmearEnd); } - /// @notice Rebalance strategy allocation. - /// @dev This function will first harvest yield, gulps and update interest. - /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. - /// If current allocation is less than target allocation, the aggregator will: - /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash - /// - If all the available cash is greater than the max deposit, deposit the max deposit - /// @param _strategy Address of strategy to rebalance. - function _rebalance(address _strategy) internal { - if (_strategy == address(0)) { - return; //nothing to rebalance as this is the cash reserve - } - - _callHookTarget(REBALANCE, _msgSender()); - - _harvest(_strategy); - - Strategy memory strategyData = strategies[_strategy]; - - // no rebalance if strategy have an allocated amount greater than cap - if ((strategyData.cap > 0) && (strategyData.allocated >= strategyData.cap)) return; - - uint256 totalAllocationPointsCache = totalAllocationPoints; - uint256 totalAssetsAllocatableCache = totalAssetsAllocatable(); - uint256 targetAllocation = - totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; - - if ((strategyData.cap > 0) && (targetAllocation > strategyData.cap)) targetAllocation = strategyData.cap; - - uint256 amountToRebalance; - if (strategyData.allocated > targetAllocation) { - // Withdraw - amountToRebalance = strategyData.allocated - targetAllocation; - - uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(address(this)); - if (amountToRebalance > maxWithdraw) { - amountToRebalance = maxWithdraw; - } - - IERC4626(_strategy).withdraw(amountToRebalance, address(this), address(this)); - strategies[_strategy].allocated = (strategyData.allocated - amountToRebalance).toUint120(); - totalAllocated -= amountToRebalance; - } else if (strategyData.allocated < targetAllocation) { - // Deposit - uint256 targetCash = - totalAssetsAllocatableCache * strategies[address(0)].allocationPoints / totalAllocationPointsCache; - uint256 currentCash = totalAssetsAllocatableCache - totalAllocated; - - // Calculate available cash to put in strategies - uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; - - amountToRebalance = targetAllocation - strategyData.allocated; - if (amountToRebalance > cashAvailable) { - amountToRebalance = cashAvailable; - } - - uint256 maxDeposit = IERC4626(_strategy).maxDeposit(address(this)); - if (amountToRebalance > maxDeposit) { - amountToRebalance = maxDeposit; - } - - if (amountToRebalance == 0) { - return; // No cash to deposit - } - - // Do required approval (safely) and deposit - IERC20(asset()).safeApprove(_strategy, amountToRebalance); - IERC4626(_strategy).deposit(amountToRebalance, address(this)); - strategies[_strategy].allocated = uint120(strategyData.allocated + amountToRebalance); - totalAllocated += amountToRebalance; - } - - emit Rebalance(_strategy, strategyData.allocated, targetAllocation, amountToRebalance); - } - function _harvest(address _strategy) internal { Strategy memory strategyData = strategies[_strategy]; diff --git a/src/Rebalancer.sol b/src/Rebalancer.sol new file mode 100644 index 00000000..479892ef --- /dev/null +++ b/src/Rebalancer.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; +import {IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; + +contract Rebalancer { + /// @notice Rebalance strategy allocation. + /// @dev This function will first harvest yield, gulps and update interest. + /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. + /// If current allocation is less than target allocation, the aggregator will: + /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash + /// - If all the available cash is greater than the max deposit, deposit the max deposit + function rebalance(address _curatedVault, address _strategy) external { + _rebalance(_curatedVault, _strategy); + } + + function rebalanceMultipleStrategies(address _curatedVault, address[] calldata strategies) external { + for (uint256 i; i < strategies.length; ++i) { + _rebalance(_curatedVault, strategies[i]); + } + } + + function _rebalance(address _curatedVault, address _strategy) private { + if (_strategy == address(0)) { + return; //nothing to rebalance as that's the cash reserve + } + + // _callHookTarget(REBALANCE, _msgSender()); + + IFourSixTwoSixAgg.Strategy memory strategyData = IFourSixTwoSixAgg(_curatedVault).getStrategy(_strategy); + + // no rebalance if strategy have an allocated amount greater than cap + if ((strategyData.cap > 0) && (strategyData.allocated >= strategyData.cap)) return; + + uint256 totalAllocationPointsCache = IFourSixTwoSixAgg(_curatedVault).totalAllocationPoints(); + uint256 totalAssetsAllocatableCache = IFourSixTwoSixAgg(_curatedVault).totalAssetsAllocatable(); + uint256 targetAllocation = + totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; + + if ((strategyData.cap > 0) && (targetAllocation > strategyData.cap)) targetAllocation = strategyData.cap; + + uint256 amountToRebalance; + if (strategyData.allocated > targetAllocation) { + // Withdraw + amountToRebalance = strategyData.allocated - targetAllocation; + + uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(address(this)); + if (amountToRebalance > maxWithdraw) { + amountToRebalance = maxWithdraw; + } + + IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, false); + } else if (strategyData.allocated < targetAllocation) { + // Deposit + uint256 targetCash = totalAssetsAllocatableCache + * IFourSixTwoSixAgg(_curatedVault).getStrategy(address(0)).allocationPoints / totalAllocationPointsCache; + uint256 currentCash = totalAssetsAllocatableCache - IFourSixTwoSixAgg(_curatedVault).totalAllocated(); + + // Calculate available cash to put in strategies + uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; + + amountToRebalance = targetAllocation - strategyData.allocated; + if (amountToRebalance > cashAvailable) { + amountToRebalance = cashAvailable; + } + + uint256 maxDeposit = IERC4626(_strategy).maxDeposit(address(this)); + if (amountToRebalance > maxDeposit) { + amountToRebalance = maxDeposit; + } + + if (amountToRebalance == 0) { + return; // No cash to deposit + } + + IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, true); + } + + // emit Rebalance(_strategy, strategyData.allocated, targetAllocation, amountToRebalance) + } + + // /// @notice Rebalance strategy's allocation. + // /// @param _strategy Address of strategy to rebalance. + // function rebalance(address _strategy) external { + // _rebalance(_strategy); + + // _gulp(); + // } +} diff --git a/src/interface/IFourSixTwoSixAgg.sol b/src/interface/IFourSixTwoSixAgg.sol new file mode 100644 index 00000000..68dcc390 --- /dev/null +++ b/src/interface/IFourSixTwoSixAgg.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +interface IFourSixTwoSixAgg { + /// @dev A struct that hold a strategy allocation's config + /// allocated: amount of asset deposited into strategy + /// allocationPoints: number of points allocated to this strategy + /// active: a boolean to indice if this strategy is active or not + /// cap: an optional cap in terms of deposited underlying asset. By default, it is set to 0(not activated) + struct Strategy { + uint120 allocated; + uint120 allocationPoints; + bool active; + uint120 cap; + } + + function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external; + + function getStrategy(address _strategy) external view returns (Strategy memory); + function totalAllocationPoints() external view returns (uint256); + function totalAllocated() external view returns (uint256); + function totalAssetsAllocatable() external view returns (uint256); +} diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index b605561c..223f7ff9 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import "evk/test/unit/evault/EVaultTestBase.t.sol"; import {FourSixTwoSixAgg} from "../../src/FourSixTwoSixAgg.sol"; +import {Rebalancer} from "../../src/Rebalancer.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; import {Hooks} from "../../src/Hooks.sol"; @@ -15,6 +16,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { address manager; FourSixTwoSixAgg fourSixTwoSixAgg; + Rebalancer rebalancer; function setUp() public virtual override { super.setUp(); @@ -24,6 +26,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { user2 = makeAddr("User_2"); vm.startPrank(deployer); + rebalancer = new Rebalancer(); fourSixTwoSixAgg = new FourSixTwoSixAgg( evc, address(0), @@ -41,6 +44,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER_ROLE_ADMINROLE(), deployer); // grant roles to manager fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager); @@ -49,6 +53,9 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE(), manager); + // grant rebalancing role + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER_ROLE(), address(rebalancer)); + vm.stopPrank(); } diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 2da74da6..3bd66ae9 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -55,7 +55,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -134,7 +134,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -233,7 +233,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - fourSixTwoSixAgg.rebalanceMultipleStrategies(strategiesToRebalance); + rebalancer.rebalanceMultipleStrategies(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); @@ -319,7 +319,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -421,7 +421,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - fourSixTwoSixAgg.rebalanceMultipleStrategies(strategiesToRebalance); + rebalancer.rebalanceMultipleStrategies(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); @@ -552,7 +552,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - fourSixTwoSixAgg.rebalanceMultipleStrategies(strategiesToRebalance); + rebalancer.rebalanceMultipleStrategies(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 238dbb93..ece89ae4 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -80,7 +80,7 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index a364f8d0..1a9172b6 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -80,7 +80,7 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -98,7 +98,7 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { uint256 strategyAllocatedBefore = (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated; - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); vm.stopPrank(); assertEq(strategyAllocatedBefore, (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated); @@ -141,7 +141,7 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), cap); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), cap); diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index 3f6da94d..2f826be4 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -44,7 +44,7 @@ contract GulpTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index c660dac4..52c6128e 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -44,7 +44,7 @@ contract HarvestTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index cbac406e..a5fd0d93 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -54,7 +54,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -100,7 +100,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { ); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), eTSTMaxDeposit); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTMaxDeposit); @@ -131,7 +131,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into first strategy vm.warp(block.timestamp + 86400); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); // create new strategy & add it IEVault eTSTsecondary; @@ -161,7 +161,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { uint256 expectedStrategyCash = currentCash - targetCash; vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTSTsecondary)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTSTsecondary)); // assertEq(fourSixTwoSixAgg.totalAllocated(), eTSTsecondaryMaxDeposit); assertEq( @@ -208,7 +208,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { ); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -239,7 +239,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); // decrease allocation points uint256 newAllocationPoints = 300e18; @@ -256,7 +256,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -291,7 +291,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); // decrease allocation points uint256 newAllocationPoints = 300e18; @@ -314,7 +314,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { ); vm.prank(user1); - fourSixTwoSixAgg.rebalance(address(eTST)); + rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); // assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); // assertEq( From 707ed8fc1f6e1b07d936fdc38018032d27b72211 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:41:53 +0300 Subject: [PATCH 144/316] remove modifier --- src/FourSixTwoSixAgg.sol | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index f48ae71c..a1b0c837 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -103,14 +103,6 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 event AccruePerformanceFee(address indexed feeRecipient, uint256 performanceFee, uint256 yield, uint256 feeShares); event SetStrategyCap(address indexed strategy, uint256 cap); - /// @notice Modifier to require an account status check on the EVC. - /// @dev Calls `requireAccountStatusCheck` function from EVC for the specified account after the function body. - /// @param account The address of the account to check. - modifier requireAccountStatusCheck(address account) { - _; - evc.requireAccountStatusCheck(account); - } - /// @dev Non reentrancy modifier for interest rate updates modifier nonReentrant() { if (esrSlot.locked == REENTRANCYLOCK__LOCKED) revert Reentrancy(); @@ -456,10 +448,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 public override (ERC20, IERC20) nonReentrant - requireAccountStatusCheck(_msgSender()) returns (bool) { - return super.transfer(to, amount); + super.transfer(to, amount); + + _requireAccountStatusCheck(_msgSender()); + + return true; } /// @notice Transfers a certain amount of tokens from a sender to a recipient. @@ -471,10 +466,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 public override (ERC20, IERC20) nonReentrant - requireAccountStatusCheck(from) returns (bool) { - return super.transferFrom(from, to, amount); + super.transferFrom(from, to, amount); + + _requireAccountStatusCheck(from); + + return true; } /// @dev See {IERC4626-deposit}. @@ -493,12 +491,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 public override nonReentrant - requireAccountStatusCheck(owner) returns (uint256 shares) { // Move interest to totalAssetsDeposited _updateInterestAccrued(); - return super.withdraw(assets, receiver, owner); + shares = super.withdraw(assets, receiver, owner); + + _requireAccountStatusCheck(owner); } /// @dev See {IERC4626-redeem}. @@ -507,12 +506,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 public override nonReentrant - requireAccountStatusCheck(owner) returns (uint256 assets) { // Move interest to totalAssetsDeposited _updateInterestAccrued(); - return super.redeem(shares, receiver, owner); + assets = super.redeem(shares, receiver, owner); + + _requireAccountStatusCheck(owner); } /// @notice Set hooks contract and hooked functions. @@ -736,4 +736,11 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 function _msgSender() internal view override (Context, EVCUtil) returns (address) { return EVCUtil._msgSender(); } + + /// @notice Function to require an account status check on the EVC. + /// @dev Calls `requireAccountStatusCheck` function from EVC for the specified account after the function body. + /// @param _account The address of the account to check. + function _requireAccountStatusCheck(address _account) private { + evc.requireAccountStatusCheck(_account); + } } From 6791fad20ae5d1dde8b8c1ccb132a6f323c3b55f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:21:07 +0300 Subject: [PATCH 145/316] more refactoring --- foundry.toml | 2 +- src/FourSixTwoSixAgg.sol | 81 ++++++++++++-------------- src/Rebalancer.sol | 13 +++-- src/interface/IFourSixTwoSixAgg.sol | 2 + test/common/FourSixTwoSixAggBase.t.sol | 67 ++++++++++----------- test/e2e/BalanceForwarderE2ETest.t.sol | 20 +++---- 6 files changed, 90 insertions(+), 95 deletions(-) diff --git a/foundry.toml b/foundry.toml index da8c522e..518361a2 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,7 +4,7 @@ out = "out" libs = ["lib"] test = 'test' optimizer = true -optimizer_runs = 20_000 +optimizer_runs = 1000 # solc = "0.8.0" gas_reports = ["*"] fs_permissions = [{ access = "read", path = "./"}] diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index a1b0c837..d9dc8055 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -44,18 +44,18 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 uint8 internal constant REENTRANCYLOCK__LOCKED = 2; // Roles - bytes32 public constant STRATEGY_MANAGER_ROLE = keccak256("STRATEGY_MANAGER_ROLE"); - bytes32 public constant STRATEGY_MANAGER_ROLE_ADMINROLE = keccak256("STRATEGY_MANAGER_ROLE_ADMINROLE"); - bytes32 public constant WITHDRAW_QUEUE_MANAGER_ROLE = keccak256("WITHDRAW_QUEUE_MANAGER_ROLE"); - bytes32 public constant WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE = keccak256("WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE"); - bytes32 public constant STRATEGY_ADDER_ROLE = keccak256("STRATEGY_ADDER_ROLE"); - bytes32 public constant STRATEGY_ADDER_ROLE_ADMINROLE = keccak256("STRATEGY_ADDER_ROLE_ADMINROLE"); - bytes32 public constant STRATEGY_REMOVER_ROLE = keccak256("STRATEGY_REMOVER_ROLE"); - bytes32 public constant STRATEGY_REMOVER_ROLE_ADMINROLE = keccak256("STRATEGY_REMOVER_ROLE_ADMINROLE"); - bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); - bytes32 public constant MANAGER_ROLE_ADMINROLE = keccak256("MANAGER_ROLE_ADMINROLE"); - bytes32 public constant REBALANCER_ROLE = keccak256("REBALANCER_ROLE"); - bytes32 public constant REBALANCER_ROLE_ADMINROLE = keccak256("REBALANCER_ROLE_ADMINROLE"); + bytes32 public constant STRATEGY_MANAGER = keccak256("STRATEGY_MANAGER"); + bytes32 public constant STRATEGY_MANAGER_ADMIN = keccak256("STRATEGY_MANAGER_ADMIN"); + bytes32 public constant WITHDRAW_QUEUE_MANAGER = keccak256("WITHDRAW_QUEUE_MANAGER"); + bytes32 public constant WITHDRAW_QUEUE_MANAGER_ADMIN = keccak256("WITHDRAW_QUEUE_MANAGER_ADMIN"); + bytes32 public constant STRATEGY_ADDER = keccak256("STRATEGY_ADDER"); + bytes32 public constant STRATEGY_ADDER_ADMIN = keccak256("STRATEGY_ADDER_ADMIN"); + bytes32 public constant STRATEGY_REMOVER = keccak256("STRATEGY_REMOVER"); + bytes32 public constant STRATEGY_REMOVER_ADMIN = keccak256("STRATEGY_REMOVER_ADMIN"); + bytes32 public constant MANAGER = keccak256("MANAGER"); + bytes32 public constant MANAGER_ADMIN = keccak256("MANAGER_ADMIN"); + bytes32 public constant REBALANCER = keccak256("REBALANCER"); + bytes32 public constant REBALANCER_ADMIN = keccak256("REBALANCER_ADMIN"); /// @dev The maximum performanceFee the vault can have is 50% uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; @@ -161,16 +161,16 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); // Setup role admins - _setRoleAdmin(STRATEGY_MANAGER_ROLE, STRATEGY_MANAGER_ROLE_ADMINROLE); - _setRoleAdmin(WITHDRAW_QUEUE_MANAGER_ROLE, WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE); - _setRoleAdmin(STRATEGY_ADDER_ROLE, STRATEGY_ADDER_ROLE_ADMINROLE); - _setRoleAdmin(STRATEGY_REMOVER_ROLE, STRATEGY_REMOVER_ROLE_ADMINROLE); - _setRoleAdmin(MANAGER_ROLE, MANAGER_ROLE_ADMINROLE); + _setRoleAdmin(STRATEGY_MANAGER, STRATEGY_MANAGER_ADMIN); + _setRoleAdmin(WITHDRAW_QUEUE_MANAGER, WITHDRAW_QUEUE_MANAGER_ADMIN); + _setRoleAdmin(STRATEGY_ADDER, STRATEGY_ADDER_ADMIN); + _setRoleAdmin(STRATEGY_REMOVER, STRATEGY_REMOVER_ADMIN); + _setRoleAdmin(MANAGER, MANAGER_ADMIN); } /// @notice Set performance fee recipient address /// @notice @param _newFeeRecipient Recipient address - function setFeeRecipient(address _newFeeRecipient) external onlyRole(MANAGER_ROLE) { + function setFeeRecipient(address _newFeeRecipient) external onlyRole(MANAGER) { if (_newFeeRecipient == feeRecipient) revert FeeRecipientAlreadySet(); emit SetFeeRecipient(feeRecipient, _newFeeRecipient); @@ -180,7 +180,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Set performance fee (1e18 == 100%) /// @notice @param _newFee Fee rate - function setPerformanceFee(uint256 _newFee) external onlyRole(MANAGER_ROLE) { + function setPerformanceFee(uint256 _newFee) external onlyRole(MANAGER) { if (_newFee > MAX_PERFORMANCE_FEE) revert MaxPerformanceFeeExceeded(); if (feeRecipient == address(0)) revert FeeRecipientNotSet(); if (_newFee == performanceFee) revert PerformanceFeeAlreadySet(); @@ -192,7 +192,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Opt in to strategy rewards /// @param _strategy Strategy address - function optInStrategyRewards(address _strategy) external onlyRole(MANAGER_ROLE) { + function optInStrategyRewards(address _strategy) external onlyRole(MANAGER) { if (!strategies[_strategy].active) revert InactiveStrategy(); IBalanceForwarder(_strategy).enableBalanceForwarder(); @@ -202,7 +202,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Opt out of strategy rewards /// @param _strategy Strategy address - function optOutStrategyRewards(address _strategy) external onlyRole(MANAGER_ROLE) { + function optOutStrategyRewards(address _strategy) external onlyRole(MANAGER) { IBalanceForwarder(_strategy).disableBalanceForwarder(); emit OptOutStrategyRewards(_strategy); @@ -220,7 +220,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 address _reward, address _recipient, bool _forfeitRecentReward - ) external onlyRole(MANAGER_ROLE) { + ) external onlyRole(MANAGER) { address rewardStreams = IBalanceForwarder(_strategy).balanceTrackerAddress(); IRewardStreams(rewardStreams).claimReward(_rewarded, _reward, _recipient, _forfeitRecentReward); @@ -261,9 +261,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external nonReentrant - onlyRole(REBALANCER_ROLE) + onlyRole(REBALANCER) { - _harvest(_strategy); + // _harvest(_strategy); Strategy memory strategyData = strategies[_strategy]; @@ -281,13 +281,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 } /// @notice Adjust a certain strategy's allocation points. - /// @dev Can only be called by an address that have the STRATEGY_MANAGER_ROLE + /// @dev Can only be called by an address that have the STRATEGY_MANAGER /// @param _strategy address of strategy /// @param _newPoints new strategy's points function adjustAllocationPoints(address _strategy, uint256 _newPoints) external nonReentrant - onlyRole(STRATEGY_MANAGER_ROLE) + onlyRole(STRATEGY_MANAGER) { Strategy memory strategyDataCache = strategies[_strategy]; @@ -305,7 +305,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev By default, cap is set to 0, not activated. /// @param _strategy Strategy address. /// @param _cap Cap amount - function setStrategyCap(address _strategy, uint256 _cap) external nonReentrant onlyRole(STRATEGY_MANAGER_ROLE) { + function setStrategyCap(address _strategy, uint256 _cap) external nonReentrant onlyRole(STRATEGY_MANAGER) { Strategy memory strategyDataCache = strategies[_strategy]; if (!strategyDataCache.active) { @@ -318,13 +318,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 } /// @notice Swap two strategies indexes in the withdrawal queue. - /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER_ROLE. + /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER. /// @param _index1 index of first strategy /// @param _index2 index of second strategy function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external nonReentrant - onlyRole(WITHDRAW_QUEUE_MANAGER_ROLE) + onlyRole(WITHDRAW_QUEUE_MANAGER) { uint256 length = withdrawalQueue.length; if (_index1 >= length || _index2 >= length) { @@ -341,14 +341,10 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 } /// @notice Add new strategy with it's allocation points. - /// @dev Can only be called by an address that have STRATEGY_ADDER_ROLE. + /// @dev Can only be called by an address that have STRATEGY_ADDER. /// @param _strategy Address of the strategy /// @param _allocationPoints Strategy's allocation points - function addStrategy(address _strategy, uint256 _allocationPoints) - external - nonReentrant - onlyRole(STRATEGY_ADDER_ROLE) - { + function addStrategy(address _strategy, uint256 _allocationPoints) external nonReentrant onlyRole(STRATEGY_ADDER) { if (IERC4626(_strategy).asset() != asset()) { revert InvalidStrategyAsset(); } @@ -370,9 +366,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Remove strategy and set its allocation points to zero. /// @dev This function does not pull funds, `harvest()` needs to be called to withdraw - /// @dev Can only be called by an address that have the STRATEGY_REMOVER_ROLE + /// @dev Can only be called by an address that have the STRATEGY_REMOVER /// @param _strategy Address of the strategy - function removeStrategy(address _strategy) external nonReentrant onlyRole(STRATEGY_REMOVER_ROLE) { + function removeStrategy(address _strategy) external nonReentrant onlyRole(STRATEGY_REMOVER) { if (_strategy == address(0)) revert CanNotRemoveCashReserve(); Strategy storage strategyStorage = strategies[_strategy]; @@ -444,14 +440,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @param to The recipient of the transfer. /// @param amount The amount shares to transfer. /// @return A boolean indicating whether the transfer was successful. - function transfer(address to, uint256 amount) - public - override (ERC20, IERC20) - nonReentrant - returns (bool) - { + function transfer(address to, uint256 amount) public override (ERC20, IERC20) nonReentrant returns (bool) { super.transfer(to, amount); - + _requireAccountStatusCheck(_msgSender()); return true; @@ -519,7 +510,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev This funtion should be overriden to implement access control. /// @param _hookTarget Hooks contract. /// @param _hookedFns Hooked functions. - function setHooksConfig(address _hookTarget, uint32 _hookedFns) public override onlyRole(MANAGER_ROLE) { + function setHooksConfig(address _hookTarget, uint32 _hookedFns) public override onlyRole(MANAGER) { super.setHooksConfig(_hookTarget, _hookedFns); } diff --git a/src/Rebalancer.sol b/src/Rebalancer.sol index 479892ef..42d99c85 100644 --- a/src/Rebalancer.sol +++ b/src/Rebalancer.sol @@ -28,6 +28,8 @@ contract Rebalancer { // _callHookTarget(REBALANCE, _msgSender()); + IFourSixTwoSixAgg(_curatedVault).harvest(_strategy); + IFourSixTwoSixAgg.Strategy memory strategyData = IFourSixTwoSixAgg(_curatedVault).getStrategy(_strategy); // no rebalance if strategy have an allocated amount greater than cap @@ -41,16 +43,17 @@ contract Rebalancer { if ((strategyData.cap > 0) && (targetAllocation > strategyData.cap)) targetAllocation = strategyData.cap; uint256 amountToRebalance; + bool isDeposit; if (strategyData.allocated > targetAllocation) { // Withdraw amountToRebalance = strategyData.allocated - targetAllocation; - uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(address(this)); + uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(_curatedVault); if (amountToRebalance > maxWithdraw) { amountToRebalance = maxWithdraw; } - IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, false); + isDeposit = false; } else if (strategyData.allocated < targetAllocation) { // Deposit uint256 targetCash = totalAssetsAllocatableCache @@ -65,7 +68,7 @@ contract Rebalancer { amountToRebalance = cashAvailable; } - uint256 maxDeposit = IERC4626(_strategy).maxDeposit(address(this)); + uint256 maxDeposit = IERC4626(_strategy).maxDeposit(_curatedVault); if (amountToRebalance > maxDeposit) { amountToRebalance = maxDeposit; } @@ -74,9 +77,11 @@ contract Rebalancer { return; // No cash to deposit } - IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, true); + isDeposit = true; } + IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, isDeposit); + // emit Rebalance(_strategy, strategyData.allocated, targetAllocation, amountToRebalance) } diff --git a/src/interface/IFourSixTwoSixAgg.sol b/src/interface/IFourSixTwoSixAgg.sol index 68dcc390..b8323236 100644 --- a/src/interface/IFourSixTwoSixAgg.sol +++ b/src/interface/IFourSixTwoSixAgg.sol @@ -15,6 +15,8 @@ interface IFourSixTwoSixAgg { } function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external; + function gulp() external; + function harvest(address strategy) external; function getStrategy(address _strategy) external view returns (Strategy memory); function totalAllocationPoints() external view returns (uint256); diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 223f7ff9..34e98c5a 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -39,22 +39,22 @@ contract FourSixTwoSixAggBase is EVaultTestBase { ); // grant admin roles to deployer - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER_ADMIN(), deployer); // grant roles to manager - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER(), manager); // grant rebalancing role - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER_ROLE(), address(rebalancer)); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER(), address(rebalancer)); vm.stopPrank(); } @@ -67,36 +67,33 @@ contract FourSixTwoSixAggBase is EVaultTestBase { assertEq(cashReserve.active, true); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE()), - fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_MANAGER()), + fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN() ); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE()), - fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER()), + fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN() ); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE()), - fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_ADDER()), fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN() ); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE()), - fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_REMOVER()), + fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN() ); - assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.MANAGER_ROLE()), fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE() - ); - - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE(), deployer)); - - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER_ROLE(), manager)); + assertEq(fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.MANAGER()), fourSixTwoSixAgg.MANAGER_ADMIN()); + + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER_ADMIN(), deployer)); + + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER(), manager)); } function _addStrategy(address from, address strategy, uint256 allocationPoints) internal { diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index b327e1b4..75873f97 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -35,18 +35,18 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { ); // grant admin roles to deployer - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE_ADMINROLE(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE_ADMINROLE(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ADMIN(), deployer); // grant roles to manager - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ROLE(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ROLE(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; From fdd19a29779a02ebf6ec7d6d75ee6ee3c407fd35 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:47:46 +0300 Subject: [PATCH 146/316] chore: add bytecode size checks to CI --- .github/workflows/test.yml | 2 +- src/FourSixTwoSixAgg.sol | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a04ee8bc..16f20b19 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: version: nightly - name: Run foundry build - run: forge build + run: forge build --sizes - name: Run foundry fmt check run: forge fmt --check diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index d9dc8055..c63322ea 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.0; // external dep -// internal dep -import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {Context} from "@openzeppelin/utils/Context.sol"; import {ERC20, IERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; import {ERC4626, IERC4626, Math} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumerable.sol"; +import {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol"; import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; -import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; -import {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol"; +// internal dep import {Hooks} from "./Hooks.sol"; +import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; +import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens From 60356e3f6fedde80cf9e16c94de2d8064ceaa751 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:53:52 +0300 Subject: [PATCH 147/316] move event --- src/FourSixTwoSixAgg.sol | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index c63322ea..4a62cf9c 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -91,9 +91,6 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 event SetPerformanceFee(uint256 oldFee, uint256 newFee); event OptInStrategyRewards(address indexed strategy); event OptOutStrategyRewards(address indexed strategy); - event Rebalance( - address indexed strategy, uint256 currentAllocation, uint256 targetAllocation, uint256 amountToRebalance - ); event Gulp(uint256 interestLeft, uint256 interestSmearEnd); event Harvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); event AdjustAllocationPoints(address indexed strategy, uint256 oldPoints, uint256 newPoints); @@ -263,8 +260,6 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 nonReentrant onlyRole(REBALANCER) { - // _harvest(_strategy); - Strategy memory strategyData = strategies[_strategy]; if (_isDeposit) { From 88daa438f8f5b21b967abb74dd816f2ee8f296c7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 12 Jun 2024 11:11:50 +0300 Subject: [PATCH 148/316] remove rebalance hook --- src/Hooks.sol | 7 +++---- src/Rebalancer.sol | 37 ++++++++++++++++++++----------------- test/e2e/HooksE2ETest.t.sol | 6 +++--- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/Hooks.sol b/src/Hooks.sol index 17206686..c107df23 100644 --- a/src/Hooks.sol +++ b/src/Hooks.sol @@ -14,11 +14,10 @@ abstract contract Hooks { uint32 public constant DEPOSIT = 1 << 0; uint32 public constant WITHDRAW = 1 << 1; - uint32 public constant REBALANCE = 1 << 2; - uint32 public constant ADD_STRATEGY = 1 << 3; - uint32 public constant REMOVE_STRATEGY = 1 << 4; + uint32 public constant ADD_STRATEGY = 1 << 2; + uint32 public constant REMOVE_STRATEGY = 1 << 3; - uint32 constant ACTIONS_COUNTER = 1 << 5; + uint32 constant ACTIONS_COUNTER = 1 << 4; /// @dev Contract with hooks implementation address public hookTarget; diff --git a/src/Rebalancer.sol b/src/Rebalancer.sol index 42d99c85..6175a019 100644 --- a/src/Rebalancer.sol +++ b/src/Rebalancer.sol @@ -5,29 +5,40 @@ import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; contract Rebalancer { - /// @notice Rebalance strategy allocation. - /// @dev This function will first harvest yield, gulps and update interest. - /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. - /// If current allocation is less than target allocation, the aggregator will: - /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash - /// - If all the available cash is greater than the max deposit, deposit the max deposit + event Rebalance( + address indexed curatedVault, + address indexed strategy, + uint256 currentAllocation, + uint256 targetAllocation, + uint256 amountToRebalance + ); + + /// @notice Rebalance strategy allocation for a specific curated vault. + /// @param _curatedVault Curated vault address. + /// @param _strategy Strategy address. function rebalance(address _curatedVault, address _strategy) external { _rebalance(_curatedVault, _strategy); } + /// @notice Rebalance strategies allocation for a specific curated vault. + /// @param _curatedVault Curated vault address. + /// @param strategies Strategies addresses. function rebalanceMultipleStrategies(address _curatedVault, address[] calldata strategies) external { for (uint256 i; i < strategies.length; ++i) { _rebalance(_curatedVault, strategies[i]); } } + /// @dev This function will first harvest yield, gulps and update interest. + /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. + /// If current allocation is less than target allocation, the aggregator will: + /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash + /// - If all the available cash is greater than the max deposit, deposit the max deposit function _rebalance(address _curatedVault, address _strategy) private { if (_strategy == address(0)) { return; //nothing to rebalance as that's the cash reserve } - // _callHookTarget(REBALANCE, _msgSender()); - IFourSixTwoSixAgg(_curatedVault).harvest(_strategy); IFourSixTwoSixAgg.Strategy memory strategyData = IFourSixTwoSixAgg(_curatedVault).getStrategy(_strategy); @@ -82,14 +93,6 @@ contract Rebalancer { IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, isDeposit); - // emit Rebalance(_strategy, strategyData.allocated, targetAllocation, amountToRebalance) + emit Rebalance(_curatedVault, _strategy, strategyData.allocated, targetAllocation, amountToRebalance); } - - // /// @notice Rebalance strategy's allocation. - // /// @param _strategy Address of strategy to rebalance. - // function rebalance(address _strategy) external { - // _rebalance(_strategy); - - // _gulp(); - // } } diff --git a/test/e2e/HooksE2ETest.t.sol b/test/e2e/HooksE2ETest.t.sol index 62176738..efff9207 100644 --- a/test/e2e/HooksE2ETest.t.sol +++ b/test/e2e/HooksE2ETest.t.sol @@ -27,7 +27,7 @@ contract HooksE2ETest is FourSixTwoSixAggBase { function testSetHooksConfig() public { uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT() | fourSixTwoSixAgg.WITHDRAW() - | fourSixTwoSixAgg.REBALANCE() | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); + | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); vm.startPrank(manager); address hooksContract = address(new HooksContract()); @@ -42,7 +42,7 @@ contract HooksE2ETest is FourSixTwoSixAggBase { function testSetHooksConfigWithAddressZero() public { uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT() | fourSixTwoSixAgg.WITHDRAW() - | fourSixTwoSixAgg.REBALANCE() | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); + | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); vm.startPrank(manager); vm.expectRevert(Hooks.InvalidHooksTarget.selector); @@ -52,7 +52,7 @@ contract HooksE2ETest is FourSixTwoSixAggBase { function testSetHooksConfigWithNotHooksContract() public { uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT() | fourSixTwoSixAgg.WITHDRAW() - | fourSixTwoSixAgg.REBALANCE() | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); + | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); vm.startPrank(manager); address hooksContract = address(new NotHooksContract()); From 32e39cf0948cce5a673d62a3a88807cdf0c1e3b2 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 12 Jun 2024 11:44:06 +0300 Subject: [PATCH 149/316] refactor --- src/Rebalancer.sol | 13 ++----- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 18 ++++++---- test/e2e/PerformanceFeeE2ETest.t.sol | 4 ++- test/e2e/StrategyCapE2ETest.t.sol | 12 +++++-- test/unit/GulpTest.t.sol | 4 ++- test/unit/HarvestTest.t.sol | 4 ++- test/unit/RebalanceTest.t.sol | 34 ++++++++++++++----- 7 files changed, 58 insertions(+), 31 deletions(-) diff --git a/src/Rebalancer.sol b/src/Rebalancer.sol index 6175a019..f9fdec6c 100644 --- a/src/Rebalancer.sol +++ b/src/Rebalancer.sol @@ -5,7 +5,7 @@ import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; contract Rebalancer { - event Rebalance( + event ExecuteRebalance( address indexed curatedVault, address indexed strategy, uint256 currentAllocation, @@ -13,17 +13,10 @@ contract Rebalancer { uint256 amountToRebalance ); - /// @notice Rebalance strategy allocation for a specific curated vault. - /// @param _curatedVault Curated vault address. - /// @param _strategy Strategy address. - function rebalance(address _curatedVault, address _strategy) external { - _rebalance(_curatedVault, _strategy); - } - /// @notice Rebalance strategies allocation for a specific curated vault. /// @param _curatedVault Curated vault address. /// @param strategies Strategies addresses. - function rebalanceMultipleStrategies(address _curatedVault, address[] calldata strategies) external { + function executeRebalance(address _curatedVault, address[] calldata strategies) external { for (uint256 i; i < strategies.length; ++i) { _rebalance(_curatedVault, strategies[i]); } @@ -93,6 +86,6 @@ contract Rebalancer { IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, isDeposit); - emit Rebalance(_curatedVault, _strategy, strategyData.allocated, targetAllocation, amountToRebalance); + emit ExecuteRebalance(_curatedVault, _strategy, strategyData.allocated, targetAllocation, amountToRebalance); } } diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 3bd66ae9..8aae2567 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -55,7 +55,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -134,7 +136,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -233,7 +237,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.rebalanceMultipleStrategies(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); @@ -319,7 +323,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -421,7 +427,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.rebalanceMultipleStrategies(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); @@ -552,7 +558,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.rebalanceMultipleStrategies(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index ece89ae4..f8cec8a9 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -80,7 +80,9 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index 1a9172b6..e0235ecd 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -80,7 +80,9 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -98,7 +100,9 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { uint256 strategyAllocatedBefore = (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated; - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); vm.stopPrank(); assertEq(strategyAllocatedBefore, (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated); @@ -141,7 +145,9 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), cap); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), cap); diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index 2f826be4..a31f4b92 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -44,7 +44,9 @@ contract GulpTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index 52c6128e..7ee54b8d 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -44,7 +44,9 @@ contract HarvestTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index a5fd0d93..5342e9b0 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -54,7 +54,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -100,7 +102,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { ); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), eTSTMaxDeposit); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTMaxDeposit); @@ -131,7 +135,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into first strategy vm.warp(block.timestamp + 86400); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); // create new strategy & add it IEVault eTSTsecondary; @@ -161,7 +167,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { uint256 expectedStrategyCash = currentCash - targetCash; vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTSTsecondary)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTSTsecondary); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); // assertEq(fourSixTwoSixAgg.totalAllocated(), eTSTsecondaryMaxDeposit); assertEq( @@ -208,7 +216,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { ); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -239,7 +249,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; @@ -256,7 +268,8 @@ contract RebalanceTest is FourSixTwoSixAggBase { / fourSixTwoSixAgg.totalAllocationPoints(); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); @@ -291,7 +304,9 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; @@ -314,7 +329,8 @@ contract RebalanceTest is FourSixTwoSixAggBase { ); vm.prank(user1); - rebalancer.rebalance(address(fourSixTwoSixAgg), address(eTST)); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); // assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); // assertEq( From a436765d2bebc53064e11249b501a430bbab70c0 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 12 Jun 2024 12:15:52 +0300 Subject: [PATCH 150/316] add event --- src/FourSixTwoSixAgg.sol | 3 +++ src/Rebalancer.sol | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 4a62cf9c..179af3b1 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -99,6 +99,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 event RemoveStrategy(address indexed _strategy); event AccruePerformanceFee(address indexed feeRecipient, uint256 performanceFee, uint256 yield, uint256 feeShares); event SetStrategyCap(address indexed strategy, uint256 cap); + event Rebalance(address indexed strategy, uint256 _amountToRebalance, bool _isDeposit); /// @dev Non reentrancy modifier for interest rate updates modifier nonReentrant() { @@ -273,6 +274,8 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 strategies[_strategy].allocated = (strategyData.allocated - _amountToRebalance).toUint120(); totalAllocated -= _amountToRebalance; } + + emit Rebalance(_strategy, _amountToRebalance, _isDeposit); } /// @notice Adjust a certain strategy's allocation points. diff --git a/src/Rebalancer.sol b/src/Rebalancer.sol index f9fdec6c..2be3806f 100644 --- a/src/Rebalancer.sol +++ b/src/Rebalancer.sol @@ -15,10 +15,10 @@ contract Rebalancer { /// @notice Rebalance strategies allocation for a specific curated vault. /// @param _curatedVault Curated vault address. - /// @param strategies Strategies addresses. - function executeRebalance(address _curatedVault, address[] calldata strategies) external { - for (uint256 i; i < strategies.length; ++i) { - _rebalance(_curatedVault, strategies[i]); + /// @param _strategies Strategies addresses. + function executeRebalance(address _curatedVault, address[] calldata _strategies) external { + for (uint256 i; i < _strategies.length; ++i) { + _rebalance(_curatedVault, _strategies[i]); } } @@ -27,6 +27,8 @@ contract Rebalancer { /// If current allocation is less than target allocation, the aggregator will: /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash /// - If all the available cash is greater than the max deposit, deposit the max deposit + /// @param _curatedVault Curated vault address. + /// @param _strategy Strategy address. function _rebalance(address _curatedVault, address _strategy) private { if (_strategy == address(0)) { return; //nothing to rebalance as that's the cash reserve From b0f88899f1dc189c0f7997bfac25e5f863c05bcd Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 12 Jun 2024 12:38:50 +0300 Subject: [PATCH 151/316] remove cachedBalanceTracker --- src/BalanceForwarder.sol | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/BalanceForwarder.sol b/src/BalanceForwarder.sol index 0c7d1d64..1ceac9c6 100644 --- a/src/BalanceForwarder.sol +++ b/src/BalanceForwarder.sol @@ -48,13 +48,11 @@ abstract contract BalanceForwarder is IBalanceForwarder { } function _enableBalanceForwarder(address _sender, uint256 _senderBalance) internal { - address cachedBalanceTracker = address(balanceTracker); - - if (cachedBalanceTracker == address(0)) revert NotSupported(); + if (address(balanceTracker) == address(0)) revert NotSupported(); if (isBalanceForwarderEnabled[_sender]) revert AlreadyEnabled(); isBalanceForwarderEnabled[_sender] = true; - IBalanceTracker(cachedBalanceTracker).balanceTrackerHook(_sender, _senderBalance, false); + balanceTracker.balanceTrackerHook(_sender, _senderBalance, false); emit EnableBalanceForwarder(_sender); } @@ -63,13 +61,11 @@ abstract contract BalanceForwarder is IBalanceForwarder { /// @dev Only the authenticated account can disable balance forwarding for itself /// @dev Should call the IBalanceTracker hook with the account's balance of 0 function _disableBalanceForwarder(address _sender) internal { - address cachedBalanceTracker = address(balanceTracker); - - if (cachedBalanceTracker == address(0)) revert NotSupported(); + if (address(balanceTracker) == address(0)) revert NotSupported(); if (!isBalanceForwarderEnabled[_sender]) revert AlreadyDisabled(); isBalanceForwarderEnabled[_sender] = false; - IBalanceTracker(cachedBalanceTracker).balanceTrackerHook(_sender, 0, false); + balanceTracker.balanceTrackerHook(_sender, 0, false); emit DisableBalanceForwarder(_sender); } From 43f60e683dd6fa3bbc33de60bfd96821e09ad991 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 12 Jun 2024 12:40:12 +0300 Subject: [PATCH 152/316] remove duplicate params --- src/FourSixTwoSixAgg.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 179af3b1..07a0a040 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -208,20 +208,18 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Claim a specific strategy rewards /// @param _strategy Strategy address. - /// @param _rewarded The address of the rewarded token. /// @param _reward The address of the reward token. /// @param _recipient The address to receive the claimed reward tokens. /// @param _forfeitRecentReward Whether to forfeit the recent rewards and not update the accumulator. function claimStrategyReward( address _strategy, - address _rewarded, address _reward, address _recipient, bool _forfeitRecentReward ) external onlyRole(MANAGER) { address rewardStreams = IBalanceForwarder(_strategy).balanceTrackerAddress(); - IRewardStreams(rewardStreams).claimReward(_rewarded, _reward, _recipient, _forfeitRecentReward); + IRewardStreams(rewardStreams).claimReward(_strategy, _reward, _recipient, _forfeitRecentReward); } /// @notice Enables balance forwarding for sender From f591ebff30d7ef1a3ab447c6c28fee249ddf64ed Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 12 Jun 2024 13:03:51 +0300 Subject: [PATCH 153/316] minor gas opt; remove account status check --- src/FourSixTwoSixAgg.sol | 73 +++++--------------------- test/common/FourSixTwoSixAggBase.t.sol | 2 +- test/e2e/BalanceForwarderE2ETest.t.sol | 2 +- 3 files changed, 16 insertions(+), 61 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 07a0a040..383bf19c 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -8,7 +8,7 @@ import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; import {ERC4626, IERC4626, Math} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumerable.sol"; import {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol"; -import {EVCUtil, IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; +import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; // internal dep import {Hooks} from "./Hooks.sol"; @@ -119,7 +119,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @param _initialStrategies An array of initial strategies addresses /// @param _initialStrategiesAllocationPoints An array of initial strategies allocation points constructor( - IEVC _evc, + address _evc, address _balanceTracker, address _asset, string memory _name, @@ -127,7 +127,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 uint256 _initialCashAllocationPoints, address[] memory _initialStrategies, uint256[] memory _initialStrategiesAllocationPoints - ) BalanceForwarder(_balanceTracker) EVCUtil(address(_evc)) ERC4626(IERC20(_asset)) ERC20(_name, _symbol) { + ) BalanceForwarder(_balanceTracker) EVCUtil(_evc) ERC4626(IERC20(_asset)) ERC20(_name, _symbol) { esrSlot.locked = REENTRANCYLOCK__UNLOCKED; if (_initialStrategies.length != _initialStrategiesAllocationPoints.length) revert ArrayLengthMismatch(); @@ -211,12 +211,10 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @param _reward The address of the reward token. /// @param _recipient The address to receive the claimed reward tokens. /// @param _forfeitRecentReward Whether to forfeit the recent rewards and not update the accumulator. - function claimStrategyReward( - address _strategy, - address _reward, - address _recipient, - bool _forfeitRecentReward - ) external onlyRole(MANAGER) { + function claimStrategyReward(address _strategy, address _reward, address _recipient, bool _forfeitRecentReward) + external + onlyRole(MANAGER) + { address rewardStreams = IBalanceForwarder(_strategy).balanceTrackerAddress(); IRewardStreams(rewardStreams).claimReward(_strategy, _reward, _recipient, _forfeitRecentReward); @@ -302,9 +300,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @param _strategy Strategy address. /// @param _cap Cap amount function setStrategyCap(address _strategy, uint256 _cap) external nonReentrant onlyRole(STRATEGY_MANAGER) { - Strategy memory strategyDataCache = strategies[_strategy]; - - if (!strategyDataCache.active) { + if (!strategies[_strategy].active) { revert InactiveStrategy(); } @@ -341,14 +337,14 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @param _strategy Address of the strategy /// @param _allocationPoints Strategy's allocation points function addStrategy(address _strategy, uint256 _allocationPoints) external nonReentrant onlyRole(STRATEGY_ADDER) { - if (IERC4626(_strategy).asset() != asset()) { - revert InvalidStrategyAsset(); - } - if (strategies[_strategy].active) { revert StrategyAlreadyExist(); } + if (IERC4626(_strategy).asset() != asset()) { + revert InvalidStrategyAsset(); + } + _callHookTarget(ADD_STRATEGY, _msgSender()); strategies[_strategy] = @@ -432,36 +428,6 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 return _interestAccruedFromCache(esrSlot); } - /// @notice Transfers a certain amount of tokens to a recipient. - /// @param to The recipient of the transfer. - /// @param amount The amount shares to transfer. - /// @return A boolean indicating whether the transfer was successful. - function transfer(address to, uint256 amount) public override (ERC20, IERC20) nonReentrant returns (bool) { - super.transfer(to, amount); - - _requireAccountStatusCheck(_msgSender()); - - return true; - } - - /// @notice Transfers a certain amount of tokens from a sender to a recipient. - /// @param from The sender of the transfer. - /// @param to The recipient of the transfer. - /// @param amount The amount of shares to transfer. - /// @return A boolean indicating whether the transfer was successful. - function transferFrom(address from, address to, uint256 amount) - public - override (ERC20, IERC20) - nonReentrant - returns (bool) - { - super.transferFrom(from, to, amount); - - _requireAccountStatusCheck(from); - - return true; - } - /// @dev See {IERC4626-deposit}. function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256) { return super.deposit(assets, receiver); @@ -482,9 +448,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 { // Move interest to totalAssetsDeposited _updateInterestAccrued(); - shares = super.withdraw(assets, receiver, owner); - - _requireAccountStatusCheck(owner); + return super.withdraw(assets, receiver, owner); } /// @dev See {IERC4626-redeem}. @@ -497,9 +461,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 { // Move interest to totalAssetsDeposited _updateInterestAccrued(); - assets = super.redeem(shares, receiver, owner); - - _requireAccountStatusCheck(owner); + return super.redeem(shares, receiver, owner); } /// @notice Set hooks contract and hooked functions. @@ -723,11 +685,4 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 function _msgSender() internal view override (Context, EVCUtil) returns (address) { return EVCUtil._msgSender(); } - - /// @notice Function to require an account status check on the EVC. - /// @dev Calls `requireAccountStatusCheck` function from EVC for the specified account after the function body. - /// @param _account The address of the account to check. - function _requireAccountStatusCheck(address _account) private { - evc.requireAccountStatusCheck(_account); - } } diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 34e98c5a..fa71ddb0 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -28,7 +28,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { vm.startPrank(deployer); rebalancer = new Rebalancer(); fourSixTwoSixAgg = new FourSixTwoSixAgg( - evc, + address(evc), address(0), address(assetTST), "assetTST_Agg", diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 75873f97..5e99ea0e 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -24,7 +24,7 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { trackingReward = address(new TrackingRewardStreams(address(evc), 2 weeks)); fourSixTwoSixAgg = new FourSixTwoSixAgg( - evc, + address(evc), trackingReward, address(assetTST), "assetTST_Agg", From b29462bd80ec07710ada53923e06dfb56d607482 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 12 Jun 2024 18:19:31 +0300 Subject: [PATCH 154/316] more fixes --- src/FourSixTwoSixAgg.sol | 64 +++++++++++-------- src/Rebalancer.sol | 3 - ...positRebalanceHarvestWithdrawE2ETest.t.sol | 10 +-- test/unit/GulpTest.t.sol | 4 +- 4 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 383bf19c..b2d0d3b2 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -15,6 +15,8 @@ import {Hooks} from "./Hooks.sol"; import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; +import {Test, console2, stdError} from "forge-std/Test.sol"; + /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol @@ -61,7 +63,8 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; uint256 public constant INTEREST_SMEAR = 2 weeks; - ESRSlot internal esrSlot; + /// @dev store the interest rate smearing params + ESR internal esrSlot; /// @dev Total amount of _asset deposited into FourSixTwoSixAgg contract uint256 public totalAssetsDeposited; @@ -80,7 +83,12 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev Mapping between strategy address and it's allocation config mapping(address => Strategy) internal strategies; - struct ESRSlot { + /// @dev Euler saving rate struct + /// lastInterestUpdate: last timestamo where interest was updated. + /// interestSmearEnd: timestamp when the smearing of interest end. + /// interestLeft: amount of interest left to smear. + /// locked: if locked or not for update. + struct ESR { uint40 lastInterestUpdate; uint40 interestSmearEnd; uint168 interestLeft; @@ -190,7 +198,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Opt in to strategy rewards /// @param _strategy Strategy address - function optInStrategyRewards(address _strategy) external onlyRole(MANAGER) { + function optInStrategyRewards(address _strategy) external onlyRole(MANAGER) nonReentrant { if (!strategies[_strategy].active) revert InactiveStrategy(); IBalanceForwarder(_strategy).enableBalanceForwarder(); @@ -200,7 +208,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Opt out of strategy rewards /// @param _strategy Strategy address - function optOutStrategyRewards(address _strategy) external onlyRole(MANAGER) { + function optOutStrategyRewards(address _strategy) external onlyRole(MANAGER) nonReentrant { IBalanceForwarder(_strategy).disableBalanceForwarder(); emit OptOutStrategyRewards(_strategy); @@ -214,6 +222,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 function claimStrategyReward(address _strategy, address _reward, address _recipient, bool _forfeitRecentReward) external onlyRole(MANAGER) + nonReentrant { address rewardStreams = IBalanceForwarder(_strategy).balanceTrackerAddress(); @@ -261,7 +270,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 if (_isDeposit) { // Do required approval (safely) and deposit - IERC20(asset()).safeApprove(_strategy, _amountToRebalance); + IERC20(asset()).safeIncreaseAllowance(_strategy, _amountToRebalance); IERC4626(_strategy).deposit(_amountToRebalance, address(this)); strategies[_strategy].allocated = uint120(strategyData.allocated + _amountToRebalance); totalAllocated += _amountToRebalance; @@ -393,8 +402,8 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 } /// @notice update accrued interest - /// @return struct ESRSlot struct - function updateInterestAccrued() external returns (ESRSlot memory) { + /// @return struct ESR struct + function updateInterestAccrued() external returns (ESR memory) { return _updateInterestAccrued(); } @@ -416,9 +425,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 return withdrawalQueue.length; } - /// @notice Return the ESRSlot struct - /// @return ESRSlot struct - function getESRSlot() external view returns (ESRSlot memory) { + /// @notice Return the ESR struct + /// @return ESR struct + function getESRSlot() external view returns (ESR memory) { return esrSlot; } @@ -468,14 +477,14 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev This funtion should be overriden to implement access control. /// @param _hookTarget Hooks contract. /// @param _hookedFns Hooked functions. - function setHooksConfig(address _hookTarget, uint32 _hookedFns) public override onlyRole(MANAGER) { + function setHooksConfig(address _hookTarget, uint32 _hookedFns) public override onlyRole(MANAGER) nonReentrant { super.setHooksConfig(_hookTarget, _hookedFns); } /// @notice update accrued interest. - /// @return struct ESRSlot struct. - function _updateInterestAccrued() internal returns (ESRSlot memory) { - ESRSlot memory esrSlotCache = esrSlot; + /// @return struct ESR struct. + function _updateInterestAccrued() internal returns (ESR memory) { + ESR memory esrSlotCache = esrSlot; uint256 accruedInterest = _interestAccruedFromCache(esrSlotCache); // it's safe to down-cast because the accrued interest is a fraction of interest left esrSlotCache.interestLeft -= uint168(accruedInterest); @@ -529,7 +538,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 revert NotEnoughAssets(); } + console2.log("finished strategies withdraw"); _gulp(); + console2.log("gulped already"); super._withdraw(caller, receiver, owner, assets, shares); } @@ -545,15 +556,12 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 _harvest(address(strategy)); - Strategy storage strategyStorage = strategies[address(strategy)]; - uint256 underlyingBalance = strategy.maxWithdraw(address(this)); - uint256 desiredAssets = _targetBalance - _currentBalance; uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; // Update allocated assets - strategyStorage.allocated -= uint120(withdrawAmount); + strategies[address(strategy)].allocated -= uint120(withdrawAmount); totalAllocated -= withdrawAmount; // update assetsRetrieved @@ -572,7 +580,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev gulp positive yield and increment the left interest function _gulp() internal { - ESRSlot memory esrSlotCache = _updateInterestAccrued(); + ESR memory esrSlotCache = _updateInterestAccrued(); if (totalAssetsDeposited == 0) return; uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft; @@ -592,28 +600,28 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 } function _harvest(address _strategy) internal { - Strategy memory strategyData = strategies[_strategy]; + uint120 strategyAllocatedAmount = strategies[_strategy].allocated; - if (strategyData.allocated == 0) return; + if (strategyAllocatedAmount == 0) return; uint256 underlyingBalance = IERC4626(_strategy).maxWithdraw(address(this)); - if (underlyingBalance == strategyData.allocated) { + if (underlyingBalance == strategyAllocatedAmount) { return; - } else if (underlyingBalance > strategyData.allocated) { + } else if (underlyingBalance > strategyAllocatedAmount) { // There's yield! - uint256 yield = underlyingBalance - strategyData.allocated; + uint256 yield = underlyingBalance - strategyAllocatedAmount; strategies[_strategy].allocated = uint120(underlyingBalance); totalAllocated += yield; _accruePerformanceFee(yield); } else { - uint256 loss = strategyData.allocated - underlyingBalance; + uint256 loss = strategyAllocatedAmount - underlyingBalance; strategies[_strategy].allocated = uint120(underlyingBalance); totalAllocated -= loss; - ESRSlot memory esrSlotCache = esrSlot; + ESR memory esrSlotCache = esrSlot; if (esrSlotCache.interestLeft >= loss) { esrSlotCache.interestLeft -= uint168(loss); } else { @@ -623,7 +631,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 esrSlot = esrSlotCache; } - emit Harvest(_strategy, underlyingBalance, strategyData.allocated); + emit Harvest(_strategy, underlyingBalance, strategyAllocatedAmount); } function _accruePerformanceFee(uint256 _yield) internal { @@ -660,7 +668,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev Get accrued interest without updating it. /// @param esrSlotCache Cached esrSlot /// @return uint256 accrued interest - function _interestAccruedFromCache(ESRSlot memory esrSlotCache) internal view returns (uint256) { + function _interestAccruedFromCache(ESR memory esrSlotCache) internal view returns (uint256) { // If distribution ended, full amount is accrued if (block.timestamp >= esrSlotCache.interestSmearEnd) { return esrSlotCache.interestLeft; diff --git a/src/Rebalancer.sol b/src/Rebalancer.sol index 2be3806f..089ba335 100644 --- a/src/Rebalancer.sol +++ b/src/Rebalancer.sol @@ -38,9 +38,6 @@ contract Rebalancer { IFourSixTwoSixAgg.Strategy memory strategyData = IFourSixTwoSixAgg(_curatedVault).getStrategy(_strategy); - // no rebalance if strategy have an allocated amount greater than cap - if ((strategyData.cap > 0) && (strategyData.allocated >= strategyData.cap)) return; - uint256 totalAllocationPointsCache = IFourSixTwoSixAgg(_curatedVault).totalAllocationPoints(); uint256 totalAssetsAllocatableCache = IFourSixTwoSixAgg(_curatedVault).totalAssetsAllocatable(); uint256 targetAllocation = diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 8aae2567..2ed48570 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -96,11 +96,11 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { vm.prank(user1); fourSixTwoSixAgg.withdraw(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); - assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, 0); + // assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0); + // assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + // assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + // assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); + // assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, 0); } } diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index a31f4b92..41c20510 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -56,7 +56,7 @@ contract GulpTest is FourSixTwoSixAggBase { function testGulpAfterNegativeYieldEqualToInterestLeft() public { fourSixTwoSixAgg.gulp(); - FourSixTwoSixAgg.ESRSlot memory ers = fourSixTwoSixAgg.getESRSlot(); + FourSixTwoSixAgg.ESR memory ers = fourSixTwoSixAgg.getESRSlot(); assertEq(fourSixTwoSixAgg.interestAccrued(), 0); assertEq(ers.interestLeft, 0); @@ -106,7 +106,7 @@ contract GulpTest is FourSixTwoSixAggBase { function testGulpAfterNegativeYieldBiggerThanInterestLeft() public { fourSixTwoSixAgg.gulp(); - FourSixTwoSixAgg.ESRSlot memory ers = fourSixTwoSixAgg.getESRSlot(); + FourSixTwoSixAgg.ESR memory ers = fourSixTwoSixAgg.getESRSlot(); assertEq(fourSixTwoSixAgg.interestAccrued(), 0); assertEq(ers.interestLeft, 0); From ec792d7ac324886df0854f2249819b91fe705a96 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 12 Jun 2024 18:23:17 +0300 Subject: [PATCH 155/316] clean --- src/FourSixTwoSixAgg.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index b2d0d3b2..0cdfbf71 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -15,8 +15,6 @@ import {Hooks} from "./Hooks.sol"; import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; -import {Test, console2, stdError} from "forge-std/Test.sol"; - /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol @@ -538,9 +536,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 revert NotEnoughAssets(); } - console2.log("finished strategies withdraw"); _gulp(); - console2.log("gulped already"); super._withdraw(caller, receiver, owner, assets, shares); } From 6b7636e0e46b7fdbd5c7287d3ca02e97ca1d64d3 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 12 Jun 2024 18:29:26 +0300 Subject: [PATCH 156/316] refactor setHooksConfig() --- src/FourSixTwoSixAgg.sol | 2 +- src/Hooks.sol | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 0cdfbf71..6d513ee5 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -476,7 +476,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @param _hookTarget Hooks contract. /// @param _hookedFns Hooked functions. function setHooksConfig(address _hookTarget, uint32 _hookedFns) public override onlyRole(MANAGER) nonReentrant { - super.setHooksConfig(_hookTarget, _hookedFns); + _setHooksConfig(_hookTarget, _hookedFns); } /// @notice update accrued interest. diff --git a/src/Hooks.sol b/src/Hooks.sol index c107df23..135e8fdf 100644 --- a/src/Hooks.sol +++ b/src/Hooks.sol @@ -32,10 +32,12 @@ abstract contract Hooks { } /// @notice Set hooks contract and hooked functions. - /// @dev This funtion should be overriden to implement access control. + /// @dev This funtion should be overriden to implement access control and call _setHooksConfig(). /// @param _hookTarget Hooks contract. /// @param _hookedFns Hooked functions. - function setHooksConfig(address _hookTarget, uint32 _hookedFns) public virtual { + function setHooksConfig(address _hookTarget, uint32 _hookedFns) public virtual; + + function _setHooksConfig(address _hookTarget, uint32 _hookedFns) internal { if (_hookTarget != address(0) && IHookTarget(_hookTarget).isHookTarget() != IHookTarget.isHookTarget.selector) { revert NotHooksContract(); } From b38c925546a61d2d7250e0aed79c6f5188ef7529 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:09:39 +0300 Subject: [PATCH 157/316] feat: pack hooks config in uint256 --- src/FourSixTwoSixAgg.sol | 14 +++++------ src/Hooks.sol | 50 +++++++++++++++++++++++++--------------- src/lib/HooksLib.sol | 14 ++++------- 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 6d513ee5..2af9b336 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -352,7 +352,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 revert InvalidStrategyAsset(); } - _callHookTarget(ADD_STRATEGY, _msgSender()); + _callHooksTarget(ADD_STRATEGY, _msgSender()); strategies[_strategy] = Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); @@ -376,7 +376,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 revert AlreadyRemoved(); } - _callHookTarget(REMOVE_STRATEGY, _msgSender()); + _callHooksTarget(REMOVE_STRATEGY, _msgSender()); totalAllocationPoints -= strategyStorage.allocationPoints; strategyStorage.active = false; @@ -473,10 +473,10 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Set hooks contract and hooked functions. /// @dev This funtion should be overriden to implement access control. - /// @param _hookTarget Hooks contract. + /// @param _hooksTarget Hooks contract. /// @param _hookedFns Hooked functions. - function setHooksConfig(address _hookTarget, uint32 _hookedFns) public override onlyRole(MANAGER) nonReentrant { - _setHooksConfig(_hookTarget, _hookedFns); + function setHooksConfig(address _hooksTarget, uint32 _hookedFns) public override onlyRole(MANAGER) nonReentrant { + _setHooksConfig(_hooksTarget, _hookedFns); } /// @notice update accrued interest. @@ -511,7 +511,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev Increate the total assets deposited, and call IERC4626._deposit() /// @dev See {IERC4626-_deposit}. function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { - _callHookTarget(DEPOSIT, caller); + _callHooksTarget(DEPOSIT, caller); totalAssetsDeposited += assets; @@ -526,7 +526,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 internal override { - _callHookTarget(WITHDRAW, caller); + _callHooksTarget(WITHDRAW, caller); totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); diff --git a/src/Hooks.sol b/src/Hooks.sol index 135e8fdf..8127a6fc 100644 --- a/src/Hooks.sol +++ b/src/Hooks.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {HooksLib, HooksType} from "./lib/HooksLib.sol"; +import {HooksLib} from "./lib/HooksLib.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; abstract contract Hooks { - using HooksLib for HooksType; + using HooksLib for uint32; error InvalidHooksTarget(); error NotHooksContract(); @@ -18,51 +18,65 @@ abstract contract Hooks { uint32 public constant REMOVE_STRATEGY = 1 << 3; uint32 constant ACTIONS_COUNTER = 1 << 4; + uint256 public constant HOOKS_MASK = 0x00000000000000000000000000000000000000000000000000000000FFFFFFFF; - /// @dev Contract with hooks implementation - address public hookTarget; - /// @dev Hooked functions - HooksType public hookedFns; + /// @dev storing the hooks target and kooked functions. + uint256 hooksConfig; + + event SetHooksConfig(address indexed hooksTarget, uint32 hookedFns); /// @notice Get the hooks contract and the hooked functions. /// @return address Hooks contract. /// @return uint32 Hooked functions. function getHooksConfig() external view returns (address, uint32) { - return (hookTarget, hookedFns.toUint32()); + return _getHooksConfig(hooksConfig); } /// @notice Set hooks contract and hooked functions. /// @dev This funtion should be overriden to implement access control and call _setHooksConfig(). - /// @param _hookTarget Hooks contract. + /// @param _hooksTarget Hooks contract. /// @param _hookedFns Hooked functions. - function setHooksConfig(address _hookTarget, uint32 _hookedFns) public virtual; + function setHooksConfig(address _hooksTarget, uint32 _hookedFns) public virtual; - function _setHooksConfig(address _hookTarget, uint32 _hookedFns) internal { - if (_hookTarget != address(0) && IHookTarget(_hookTarget).isHookTarget() != IHookTarget.isHookTarget.selector) { + /// @notice Set hooks contract and hooked functions. + /// @dev This funtion should be called when implementing setHooksConfig(). + /// @param _hooksTarget Hooks contract. + /// @param _hookedFns Hooked functions. + function _setHooksConfig(address _hooksTarget, uint32 _hookedFns) internal { + if (_hooksTarget != address(0) && IHookTarget(_hooksTarget).isHookTarget() != IHookTarget.isHookTarget.selector) + { revert NotHooksContract(); } - if (_hookedFns != 0 && _hookTarget == address(0)) { + if (_hookedFns != 0 && _hooksTarget == address(0)) { revert InvalidHooksTarget(); } if (_hookedFns >= ACTIONS_COUNTER) revert InvalidHookedFns(); - hookTarget = _hookTarget; - hookedFns = HooksType.wrap(_hookedFns); + hooksConfig = (uint256(uint160(_hooksTarget)) << 32) | uint256(_hookedFns); + + emit SetHooksConfig(_hooksTarget, _hookedFns); } /// @notice Checks whether a hook has been installed for the function and if so, invokes the hook target. - /// @param _fn Function to check hook for. + /// @param _fn Function to call the hook for. /// @param _caller Caller's address. - function _callHookTarget(uint32 _fn, address _caller) internal { - if (hookedFns.isNotSet(_fn)) return; + function _callHooksTarget(uint32 _fn, address _caller) internal { + (address target, uint32 hookedFns) = _getHooksConfig(hooksConfig); - address target = hookTarget; + if (hookedFns.isNotSet(_fn)) return; (bool success, bytes memory data) = target.call(abi.encodePacked(msg.data, _caller)); if (!success) _revertBytes(data); } + /// @notice Get the hooks contract and the hooked functions. + /// @return address Hooks contract. + /// @return uint32 Hooked functions. + function _getHooksConfig(uint256 _hooksConfig) internal pure returns (address, uint32) { + return (address(uint160(_hooksConfig >> 32)), uint32(_hooksConfig & HOOKS_MASK)); + } + /// @dev Revert with call error or EmptyError /// @param _errorMsg call revert message function _revertBytes(bytes memory _errorMsg) private pure { diff --git a/src/lib/HooksLib.sol b/src/lib/HooksLib.sol index c424ef02..7bfedbc6 100644 --- a/src/lib/HooksLib.sol +++ b/src/lib/HooksLib.sol @@ -8,18 +8,12 @@ pragma solidity ^0.8.0; /// @dev This is copied from https://github.com/euler-xyz/euler-vault-kit/blob/30b0b9e36b0a912fe430c7482e9b3bb12d180a4e/src/EVault/shared/types/Flags.sol library HooksLib { /// @dev Are *all* of the Hooks in bitMask set? - function isSet(HooksType self, uint32 bitMask) internal pure returns (bool) { - return (HooksType.unwrap(self) & bitMask) == bitMask; + function isSet(uint32 _hookedFns, uint32 _fn) internal pure returns (bool) { + return (_hookedFns & _fn) == _fn; } /// @dev Are *none* of the Hooks in bitMask set? - function isNotSet(HooksType self, uint32 bitMask) internal pure returns (bool) { - return (HooksType.unwrap(self) & bitMask) == 0; - } - - function toUint32(HooksType self) internal pure returns (uint32) { - return HooksType.unwrap(self); + function isNotSet(uint32 _hookedFns, uint32 _fn) internal pure returns (bool) { + return (_hookedFns & _fn) == 0; } } - -type HooksType is uint32; From f5c5bd1975025851a08aae0b389f424cff92aafc Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:14:06 +0300 Subject: [PATCH 158/316] uncomment asserts --- test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 2ed48570..8aae2567 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -96,11 +96,11 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { vm.prank(user1); fourSixTwoSixAgg.withdraw(amountToWithdraw, user1, user1); - // assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0); - // assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - // assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); - // assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); - // assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, 0); + assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, 0); } } From e43cff0c52ff54eefb48081b128dcb142aff3b3e Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:17:11 +0300 Subject: [PATCH 159/316] refactor --- src/FourSixTwoSixAgg.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 2af9b336..60e57cc3 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -388,7 +388,6 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 for (uint256 i = 0; i < lastStrategyIndex; ++i) { if (withdrawalQueue[i] == _strategy) { withdrawalQueue[i] = withdrawalQueue[lastStrategyIndex]; - withdrawalQueue[lastStrategyIndex] = _strategy; break; } From c048e42dd6365546007db26265e5540a539ab80c Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:43:11 +0300 Subject: [PATCH 160/316] revert on duplicate initial strategies --- src/FourSixTwoSixAgg.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 60e57cc3..daff6e86 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -39,6 +39,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 error FeeRecipientNotSet(); error FeeRecipientAlreadySet(); error CanNotRemoveCashReserve(); + error DuplicateInitialStrategy(); uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; uint8 internal constant REENTRANCYLOCK__LOCKED = 2; @@ -149,6 +150,8 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 revert InvalidStrategyAsset(); } + if (strategies[_initialStrategies[i]].active) revert DuplicateInitialStrategy(); + strategies[_initialStrategies[i]] = Strategy({ allocated: 0, allocationPoints: _initialStrategiesAllocationPoints[i].toUint120(), From 199ca1781a5c372144d905f3f56fcee0cf593590 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:45:28 +0300 Subject: [PATCH 161/316] accrue performance fee in underlying asset --- src/FourSixTwoSixAgg.sol | 24 ++++++++++++++++-------- test/e2e/PerformanceFeeE2ETest.t.sol | 3 +-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index daff6e86..12e09ab8 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -104,7 +104,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 event ReorderWithdrawalQueue(uint8 index1, uint8 index2); event AddStrategy(address indexed strategy, uint256 allocationPoints); event RemoveStrategy(address indexed _strategy); - event AccruePerformanceFee(address indexed feeRecipient, uint256 performanceFee, uint256 yield, uint256 feeShares); + event AccruePerformanceFee(address indexed feeRecipient, uint256 yield, uint256 feeAssets); event SetStrategyCap(address indexed strategy, uint256 cap); event Rebalance(address indexed strategy, uint256 _amountToRebalance, bool _isDeposit); @@ -609,10 +609,15 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 } else if (underlyingBalance > strategyAllocatedAmount) { // There's yield! uint256 yield = underlyingBalance - strategyAllocatedAmount; + uint120 accruedPerformanceFee = _accruePerformanceFee(_strategy, yield); + + if (accruedPerformanceFee != 0) { + underlyingBalance -= accruedPerformanceFee; + yield -= accruedPerformanceFee; + } + strategies[_strategy].allocated = uint120(underlyingBalance); totalAllocated += yield; - - _accruePerformanceFee(yield); } else { uint256 loss = strategyAllocatedAmount - underlyingBalance; @@ -632,19 +637,22 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 emit Harvest(_strategy, underlyingBalance, strategyAllocatedAmount); } - function _accruePerformanceFee(uint256 _yield) internal { + function _accruePerformanceFee(address _strategy, uint256 _yield) internal returns (uint120) { address cachedFeeRecipient = feeRecipient; uint256 cachedPerformanceFee = performanceFee; - if (cachedFeeRecipient == address(0) || cachedPerformanceFee == 0) return; + if (cachedFeeRecipient == address(0) || cachedPerformanceFee == 0) return 0; // `feeAssets` will be rounded down to 0 if `yield * performanceFee < 1e18`. uint256 feeAssets = Math.mulDiv(_yield, cachedPerformanceFee, 1e18, Math.Rounding.Down); - uint256 feeShares = _convertToShares(feeAssets, Math.Rounding.Down); - if (feeShares != 0) _mint(cachedFeeRecipient, feeShares); + if(feeAssets > 0) { + IERC4626(_strategy).withdraw(feeAssets, cachedFeeRecipient, address(this)); + } + + emit AccruePerformanceFee(cachedFeeRecipient, _yield, feeAssets); - emit AccruePerformanceFee(cachedFeeRecipient, cachedPerformanceFee, _yield, feeShares); + return feeAssets.toUint120(); } /// @dev Override _afterTokenTransfer hook to call IBalanceTracker.balanceTrackerHook() diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index f8cec8a9..81652b74 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -102,13 +102,12 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { } uint256 expectedPerformanceFee = yield * fourSixTwoSixAgg.performanceFee() / 1e18; - uint256 expectedPerformanceFeeShares = fourSixTwoSixAgg.convertToShares(expectedPerformanceFee); // harvest vm.prank(user1); fourSixTwoSixAgg.harvest(address(eTST)); - assertEq(fourSixTwoSixAgg.balanceOf(feeRecipient), expectedPerformanceFeeShares); + assertEq(assetTST.balanceOf(feeRecipient), expectedPerformanceFee); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { From d1329a3c31e61b3471a414751755cb6234099a64 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:46:58 +0300 Subject: [PATCH 162/316] lint --- src/FourSixTwoSixAgg.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 12e09ab8..074c0f15 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -615,7 +615,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 underlyingBalance -= accruedPerformanceFee; yield -= accruedPerformanceFee; } - + strategies[_strategy].allocated = uint120(underlyingBalance); totalAllocated += yield; } else { @@ -646,9 +646,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 // `feeAssets` will be rounded down to 0 if `yield * performanceFee < 1e18`. uint256 feeAssets = Math.mulDiv(_yield, cachedPerformanceFee, 1e18, Math.Rounding.Down); - if(feeAssets > 0) { + if (feeAssets > 0) { IERC4626(_strategy).withdraw(feeAssets, cachedFeeRecipient, address(this)); - } + } emit AccruePerformanceFee(cachedFeeRecipient, _yield, feeAssets); From 42832adc4fc8c34ca9dae120fa794ab775c8e597 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:49:15 +0300 Subject: [PATCH 163/316] use > instead of != --- src/FourSixTwoSixAgg.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 074c0f15..adc22130 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -611,7 +611,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 uint256 yield = underlyingBalance - strategyAllocatedAmount; uint120 accruedPerformanceFee = _accruePerformanceFee(_strategy, yield); - if (accruedPerformanceFee != 0) { + if (accruedPerformanceFee > 0) { underlyingBalance -= accruedPerformanceFee; yield -= accruedPerformanceFee; } From 82d34311ab0f6b658067b7b607ddc6dbdacc5fe0 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:53:43 +0300 Subject: [PATCH 164/316] test: add asserts --- test/e2e/PerformanceFeeE2ETest.t.sol | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 81652b74..f72e6eb8 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -103,11 +103,19 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { uint256 expectedPerformanceFee = yield * fourSixTwoSixAgg.performanceFee() / 1e18; + FourSixTwoSixAgg.Strategy memory strategyBeforeHarvest = fourSixTwoSixAgg.getStrategy(address(eTST)); + uint256 totalAllocatedBefore = fourSixTwoSixAgg.totalAllocated(); + // harvest vm.prank(user1); fourSixTwoSixAgg.harvest(address(eTST)); assertEq(assetTST.balanceOf(feeRecipient), expectedPerformanceFee); + assertEq( + fourSixTwoSixAgg.getStrategy(address(eTST)).allocated, + strategyBeforeHarvest.allocated + yield - expectedPerformanceFee + ); + assertEq(fourSixTwoSixAgg.totalAllocated(), totalAllocatedBefore + yield - expectedPerformanceFee); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { From a6052657b38e3060ad0239a0f06270bf6cf8b80c Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 14 Jun 2024 18:01:02 +0300 Subject: [PATCH 165/316] refactor --- src/FourSixTwoSixAgg.sol | 134 ++++++------------ src/interface/IFourSixTwoSixAgg.sol | 4 +- test/common/FourSixTwoSixAggBase.t.sol | 58 +++++--- test/e2e/BalanceForwarderE2ETest.t.sol | 36 +++-- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 5 +- .../fuzz/AdjustAllocationPointsFuzzTest.t.sol | 4 +- test/unit/AddStrategyTest.t.sol | 12 +- test/unit/AdjustAllocationPointsTest.t.sol | 4 +- test/unit/RemoveStrategy.t.sol | 8 +- test/unit/ReorderWithdrawalQueueTest.t.sol | 32 ++--- 10 files changed, 137 insertions(+), 160 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index adc22130..8ba1de5c 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -12,8 +12,9 @@ import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; // internal dep import {Hooks} from "./Hooks.sol"; -import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; +import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; +import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens @@ -26,11 +27,8 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 error Reentrancy(); error ArrayLengthMismatch(); error InitialAllocationPointsZero(); - error NotEnoughAssets(); error NegativeYield(); error InactiveStrategy(); - error OutOfBounds(); - error SameIndexes(); error InvalidStrategyAsset(); error StrategyAlreadyExist(); error AlreadyRemoved(); @@ -47,8 +45,6 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 // Roles bytes32 public constant STRATEGY_MANAGER = keccak256("STRATEGY_MANAGER"); bytes32 public constant STRATEGY_MANAGER_ADMIN = keccak256("STRATEGY_MANAGER_ADMIN"); - bytes32 public constant WITHDRAW_QUEUE_MANAGER = keccak256("WITHDRAW_QUEUE_MANAGER"); - bytes32 public constant WITHDRAW_QUEUE_MANAGER_ADMIN = keccak256("WITHDRAW_QUEUE_MANAGER_ADMIN"); bytes32 public constant STRATEGY_ADDER = keccak256("STRATEGY_ADDER"); bytes32 public constant STRATEGY_ADDER_ADMIN = keccak256("STRATEGY_ADDER_ADMIN"); bytes32 public constant STRATEGY_REMOVER = keccak256("STRATEGY_REMOVER"); @@ -76,8 +72,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev fee recipient address address public feeRecipient; - /// @dev An array of strategy addresses to withdraw from - address[] public withdrawalQueue; + address public withdrawalQueue; /// @dev Mapping between strategy address and it's allocation config mapping(address => Strategy) internal strategies; @@ -101,7 +96,6 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 event Gulp(uint256 interestLeft, uint256 interestSmearEnd); event Harvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); event AdjustAllocationPoints(address indexed strategy, uint256 oldPoints, uint256 newPoints); - event ReorderWithdrawalQueue(uint8 index1, uint8 index2); event AddStrategy(address indexed strategy, uint256 allocationPoints); event RemoveStrategy(address indexed _strategy); event AccruePerformanceFee(address indexed feeRecipient, uint256 yield, uint256 feeAssets); @@ -128,6 +122,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 constructor( address _evc, address _balanceTracker, + address _withdrawalQueue, + address _rebalancer, + address _owner, address _asset, string memory _name, string memory _symbol, @@ -140,6 +137,8 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 if (_initialStrategies.length != _initialStrategiesAllocationPoints.length) revert ArrayLengthMismatch(); if (_initialCashAllocationPoints == 0) revert InitialAllocationPointsZero(); + withdrawalQueue = _withdrawalQueue; + strategies[address(0)] = Strategy({allocated: 0, allocationPoints: _initialCashAllocationPoints.toUint120(), active: true, cap: 0}); @@ -160,19 +159,22 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 }); cachedTotalAllocationPoints += _initialStrategiesAllocationPoints[i]; - withdrawalQueue.push(_initialStrategies[i]); } totalAllocationPoints = cachedTotalAllocationPoints; + IWithdrawalQueue(withdrawalQueue).initWithdrawalQueue(_owner, _initialStrategies); // Setup DEFAULT_ADMIN - _grantRole(DEFAULT_ADMIN_ROLE, _msgSender()); + _grantRole(DEFAULT_ADMIN_ROLE, _owner); + // By default, the Rebalancer contract is assigned the REBALANCER role + _grantRole(REBALANCER, _rebalancer); // Setup role admins _setRoleAdmin(STRATEGY_MANAGER, STRATEGY_MANAGER_ADMIN); - _setRoleAdmin(WITHDRAW_QUEUE_MANAGER, WITHDRAW_QUEUE_MANAGER_ADMIN); + // _setRoleAdmin(WITHDRAW_QUEUE_MANAGER, WITHDRAW_QUEUE_MANAGER_ADMIN); _setRoleAdmin(STRATEGY_ADDER, STRATEGY_ADDER_ADMIN); _setRoleAdmin(STRATEGY_REMOVER, STRATEGY_REMOVER_ADMIN); _setRoleAdmin(MANAGER, MANAGER_ADMIN); + _setRoleAdmin(REBALANCER, REBALANCER_ADMIN); } /// @notice Set performance fee recipient address @@ -247,7 +249,8 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Harvest strategy. /// @param strategy address of strategy - function harvest(address strategy) external nonReentrant { + //TODO: is this safe without the reentrancy check + function harvest(address strategy) external { _harvest(strategy); _gulp(); @@ -319,29 +322,6 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 emit SetStrategyCap(_strategy, _cap); } - /// @notice Swap two strategies indexes in the withdrawal queue. - /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER. - /// @param _index1 index of first strategy - /// @param _index2 index of second strategy - function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) - external - nonReentrant - onlyRole(WITHDRAW_QUEUE_MANAGER) - { - uint256 length = withdrawalQueue.length; - if (_index1 >= length || _index2 >= length) { - revert OutOfBounds(); - } - - if (_index1 == _index2) { - revert SameIndexes(); - } - - (withdrawalQueue[_index1], withdrawalQueue[_index2]) = (withdrawalQueue[_index2], withdrawalQueue[_index1]); - - emit ReorderWithdrawalQueue(_index1, _index2); - } - /// @notice Add new strategy with it's allocation points. /// @dev Can only be called by an address that have STRATEGY_ADDER. /// @param _strategy Address of the strategy @@ -361,7 +341,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); totalAllocationPoints += _allocationPoints; - withdrawalQueue.push(_strategy); + IWithdrawalQueue(withdrawalQueue).addStrategyToWithdrawalQueue(_strategy); emit AddStrategy(_strategy, _allocationPoints); } @@ -386,17 +366,7 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 strategyStorage.allocationPoints = 0; // remove from withdrawalQueue - uint256 lastStrategyIndex = withdrawalQueue.length - 1; - - for (uint256 i = 0; i < lastStrategyIndex; ++i) { - if (withdrawalQueue[i] == _strategy) { - withdrawalQueue[i] = withdrawalQueue[lastStrategyIndex]; - - break; - } - } - - withdrawalQueue.pop(); + IWithdrawalQueue(withdrawalQueue).removeStrategyFromWithdrawalQueue(_strategy); emit RemoveStrategy(_strategy); } @@ -419,12 +389,6 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 return strategies[_strategy]; } - /// @notice Return the withdrawal queue length. - /// @return uint256 length - function withdrawalQueueLength() external view returns (uint256) { - return withdrawalQueue.length; - } - /// @notice Return the ESR struct /// @return ESR struct function getESRSlot() external view returns (ESR memory) { @@ -533,47 +497,43 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); - if (assetsRetrieved < assets) assetsRetrieved = _withdrawFromStrategies(assetsRetrieved, assets); if (assetsRetrieved < assets) { - revert NotEnoughAssets(); + IWithdrawalQueue(withdrawalQueue).executeWithdrawFromQueue( + caller, receiver, owner, assets, shares, assetsRetrieved + ); + } else { + _executeWithdrawFromReserve(caller, receiver, owner, assets, shares); } - - _gulp(); - - super._withdraw(caller, receiver, owner, assets, shares); } - /// @dev Withdraw needed asset amount from strategies. - /// @param _currentBalance Aggregator asset balance. - /// @param _targetBalance target balance. - /// @return uint256 current balance after withdraw. - function _withdrawFromStrategies(uint256 _currentBalance, uint256 _targetBalance) internal returns (uint256) { - uint256 numStrategies = withdrawalQueue.length; - for (uint256 i; i < numStrategies; ++i) { - IERC4626 strategy = IERC4626(withdrawalQueue[i]); - - _harvest(address(strategy)); - - uint256 underlyingBalance = strategy.maxWithdraw(address(this)); - uint256 desiredAssets = _targetBalance - _currentBalance; - uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; + // TODO: add access control + function withdrawFromStrategy(address _strategy, uint256 _withdrawAmount) external { + // Update allocated assets + strategies[_strategy].allocated -= uint120(_withdrawAmount); + totalAllocated -= _withdrawAmount; - // Update allocated assets - strategies[address(strategy)].allocated -= uint120(withdrawAmount); - totalAllocated -= withdrawAmount; - - // update assetsRetrieved - _currentBalance += withdrawAmount; + // Do actual withdraw from strategy + IERC4626(_strategy).withdraw(_withdrawAmount, address(this), address(this)); + } - // Do actual withdraw from strategy - strategy.withdraw(withdrawAmount, address(this), address(this)); + // TODO: add access control + function executeWithdrawFromReserve(address caller, address receiver, address owner, uint256 assets, uint256 shares) + external + { + _executeWithdrawFromReserve(caller, receiver, owner, assets, shares); + } - if (_currentBalance >= _targetBalance) { - break; - } - } + // TODO: add access control + function _executeWithdrawFromReserve( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal { + _gulp(); - return _currentBalance; + super._withdraw(caller, receiver, owner, assets, shares); } /// @dev gulp positive yield and increment the left interest diff --git a/src/interface/IFourSixTwoSixAgg.sol b/src/interface/IFourSixTwoSixAgg.sol index b8323236..c8fdbc84 100644 --- a/src/interface/IFourSixTwoSixAgg.sol +++ b/src/interface/IFourSixTwoSixAgg.sol @@ -17,7 +17,9 @@ interface IFourSixTwoSixAgg { function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external; function gulp() external; function harvest(address strategy) external; - + function withdrawFromStrategy(address _strategy, uint256 _withdrawAmount) external; + function executeWithdrawFromReserve(address caller, address receiver, address owner, uint256 assets, uint256 shares) + external; function getStrategy(address _strategy) external view returns (Strategy memory); function totalAllocationPoints() external view returns (uint256); function totalAllocated() external view returns (uint256); diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index fa71ddb0..a6160abb 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -6,6 +6,9 @@ import {FourSixTwoSixAgg} from "../../src/FourSixTwoSixAgg.sol"; import {Rebalancer} from "../../src/Rebalancer.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; import {Hooks} from "../../src/Hooks.sol"; +import {FourSixTwoSixAggFactory} from "../../src/FourSixTwoSixAggFactory.sol"; +import {WithdrawalQueue} from "../../src/WithdrawalQueue.sol"; +import {IWithdrawalQueue} from "../../src/interface/IWithdrawalQueue.sol"; contract FourSixTwoSixAggBase is EVaultTestBase { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; @@ -15,9 +18,12 @@ contract FourSixTwoSixAggBase is EVaultTestBase { address user2; address manager; - FourSixTwoSixAgg fourSixTwoSixAgg; + FourSixTwoSixAggFactory fourSixTwoSixAggFactory; Rebalancer rebalancer; + FourSixTwoSixAgg fourSixTwoSixAgg; + WithdrawalQueue withdrawalQueue; + function setUp() public virtual override { super.setUp(); @@ -27,34 +33,34 @@ contract FourSixTwoSixAggBase is EVaultTestBase { vm.startPrank(deployer); rebalancer = new Rebalancer(); - fourSixTwoSixAgg = new FourSixTwoSixAgg( - address(evc), - address(0), - address(assetTST), - "assetTST_Agg", - "assetTST_Agg", - CASH_RESERVE_ALLOCATION_POINTS, - new address[](0), - new uint256[](0) + fourSixTwoSixAggFactory = new FourSixTwoSixAggFactory(address(evc), address(0), address(rebalancer)); + + fourSixTwoSixAgg = FourSixTwoSixAgg( + fourSixTwoSixAggFactory.deployEulerAggregationLayer( + address(assetTST), + "assetTST_Agg", + "assetTST_Agg", + CASH_RESERVE_ALLOCATION_POINTS, + new address[](0), + new uint256[](0) + ) ); + withdrawalQueue = WithdrawalQueue(fourSixTwoSixAgg.withdrawalQueue()); // grant admin roles to deployer fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ADMIN(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER_ADMIN(), deployer); + withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER(), manager); - - // grant rebalancing role - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER(), address(rebalancer)); + withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); vm.stopPrank(); } @@ -70,10 +76,6 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_MANAGER()), fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN() ); - assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER()), - fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN() - ); assertEq( fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_ADDER()), fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN() ); @@ -82,18 +84,22 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN() ); assertEq(fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.MANAGER()), fourSixTwoSixAgg.MANAGER_ADMIN()); + assertEq( + withdrawalQueue.getRoleAdmin(withdrawalQueue.WITHDRAW_QUEUE_MANAGER()), + withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN() + ); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER_ADMIN(), deployer)); + assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER(), manager)); + assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); } function _addStrategy(address from, address strategy, uint256 allocationPoints) internal { @@ -101,12 +107,18 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.addStrategy(strategy, allocationPoints); } + function _getWithdrawalQueueLength() internal view returns (uint256) { + uint256 length = withdrawalQueue.withdrawalQueueLength(); + + return length; + } + function _getWithdrawalQueue() internal view returns (address[] memory) { - uint256 length = fourSixTwoSixAgg.withdrawalQueueLength(); + uint256 length = withdrawalQueue.withdrawalQueueLength(); address[] memory queue = new address[](length); for (uint256 i = 0; i < length; ++i) { - queue[i] = fourSixTwoSixAgg.withdrawalQueue(i); + queue[i] = withdrawalQueue.withdrawalQueue(i); } return queue; } diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 5e99ea0e..3f5ce3b1 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -8,7 +8,8 @@ import { EVault, IEVault, IRMTestDefault, - TestERC20 + TestERC20, + FourSixTwoSixAggFactory } from "../common/FourSixTwoSixAggBase.t.sol"; import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol"; @@ -23,27 +24,38 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { vm.startPrank(deployer); trackingReward = address(new TrackingRewardStreams(address(evc), 2 weeks)); - fourSixTwoSixAgg = new FourSixTwoSixAgg( - address(evc), - trackingReward, - address(assetTST), - "assetTST_Agg", - "assetTST_Agg", - CASH_RESERVE_ALLOCATION_POINTS, - new address[](0), - new uint256[](0) + // fourSixTwoSixAgg = new FourSixTwoSixAgg( + // address(evc), + // trackingReward, + // address(assetTST), + // "assetTST_Agg", + // "assetTST_Agg", + // CASH_RESERVE_ALLOCATION_POINTS, + // new address[](0), + // new uint256[](0) + // ); + fourSixTwoSixAggFactory = new FourSixTwoSixAggFactory(address(evc), trackingReward, address(rebalancer)); + fourSixTwoSixAgg = FourSixTwoSixAgg( + fourSixTwoSixAggFactory.deployEulerAggregationLayer( + address(assetTST), + "assetTST_Agg", + "assetTST_Agg", + CASH_RESERVE_ALLOCATION_POINTS, + new address[](0), + new uint256[](0) + ) ); // grant admin roles to deployer fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); + // fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ADMIN(), deployer); // grant roles to manager fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER(), manager); + // fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER(), manager); diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 8aae2567..281e441f 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -8,7 +8,8 @@ import { EVault, IEVault, IRMTestDefault, - TestERC20 + TestERC20, + WithdrawalQueue } from "../common/FourSixTwoSixAggBase.t.sol"; contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { @@ -611,7 +612,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); vm.prank(user1); - vm.expectRevert(FourSixTwoSixAgg.NotEnoughAssets.selector); + vm.expectRevert(WithdrawalQueue.NotEnoughAssets.selector); fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); } } diff --git a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol index c5415507..11cfc703 100644 --- a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol +++ b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol @@ -16,7 +16,7 @@ contract AdjustAllocationsPointsFuzzTest is FourSixTwoSixAggBase { uint256 strategyAllocationPoints = (fourSixTwoSixAgg.getStrategy(address(eTST))).allocationPoints; uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); - uint256 withdrawalQueueLengthBefore = fourSixTwoSixAgg.withdrawalQueueLength(); + uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), _newAllocationPoints); @@ -34,7 +34,7 @@ contract AdjustAllocationsPointsFuzzTest is FourSixTwoSixAggBase { totalAllocationPointsBefore + (_newAllocationPoints - strategyAllocationPoints) ); } - assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), withdrawalQueueLengthBefore); + assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore); assertEq(strategy.allocationPoints, _newAllocationPoints); } } diff --git a/test/unit/AddStrategyTest.t.sol b/test/unit/AddStrategyTest.t.sol index 97c9893b..493c13e9 100644 --- a/test/unit/AddStrategyTest.t.sol +++ b/test/unit/AddStrategyTest.t.sol @@ -12,18 +12,18 @@ contract AddStrategyTest is FourSixTwoSixAggBase { uint256 allocationPoints = 500e18; uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); - assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); + assertEq(_getWithdrawalQueueLength(), 0); _addStrategy(manager, address(eTST), allocationPoints); assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); - assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 1); + assertEq(_getWithdrawalQueueLength(), 1); } function testAddStrategy_FromUnauthorizedAddress() public { uint256 allocationPoints = 500e18; - assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); + assertEq(_getWithdrawalQueueLength(), 0); vm.expectRevert(); _addStrategy(deployer, address(eTST), allocationPoints); @@ -32,7 +32,7 @@ contract AddStrategyTest is FourSixTwoSixAggBase { function testAddStrategy_WithInvalidAsset() public { uint256 allocationPoints = 500e18; - assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); + assertEq(_getWithdrawalQueueLength(), 0); vm.expectRevert(); _addStrategy(manager, address(eTST2), allocationPoints); @@ -42,12 +42,12 @@ contract AddStrategyTest is FourSixTwoSixAggBase { uint256 allocationPoints = 500e18; uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); - assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 0); + assertEq(_getWithdrawalQueueLength(), 0); _addStrategy(manager, address(eTST), allocationPoints); assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); - assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), 1); + assertEq(_getWithdrawalQueueLength(), 1); vm.expectRevert(); _addStrategy(manager, address(eTST), allocationPoints); diff --git a/test/unit/AdjustAllocationPointsTest.t.sol b/test/unit/AdjustAllocationPointsTest.t.sol index da9ef58c..6e7f66fd 100644 --- a/test/unit/AdjustAllocationPointsTest.t.sol +++ b/test/unit/AdjustAllocationPointsTest.t.sol @@ -15,7 +15,7 @@ contract AdjustAllocationsPointsTest is FourSixTwoSixAggBase { function testAdjustAllocationPoints() public { uint256 newAllocationPoints = 859e18; uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); - uint256 withdrawalQueueLengthBefore = fourSixTwoSixAgg.withdrawalQueueLength(); + uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), newAllocationPoints); @@ -26,7 +26,7 @@ contract AdjustAllocationsPointsTest is FourSixTwoSixAggBase { fourSixTwoSixAgg.totalAllocationPoints(), totalAllocationPointsBefore + (newAllocationPoints - initialStrategyAllocationPoints) ); - assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), withdrawalQueueLengthBefore); + assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore); assertEq(strategy.allocationPoints, newAllocationPoints); } diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index 9bfb2f66..d90ee41b 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -17,7 +17,7 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { function testRemoveStrategy() public { uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); - uint256 withdrawalQueueLengthBefore = fourSixTwoSixAgg.withdrawalQueueLength(); + uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); fourSixTwoSixAgg.removeStrategy(address(eTST)); @@ -27,7 +27,7 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { assertEq(strategyAfter.active, false); assertEq(strategyAfter.allocationPoints, 0); assertEq(fourSixTwoSixAgg.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); - assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), withdrawalQueueLengthBefore - 1); + assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore - 1); } function testRemoveStrategyWithMultipleStrategies() public { @@ -37,7 +37,7 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { _addStrategy(manager, address(anotherStrategy), strategyAllocationPoints); uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); - uint256 withdrawalQueueLengthBefore = fourSixTwoSixAgg.withdrawalQueueLength(); + uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); fourSixTwoSixAgg.removeStrategy(address(eTST)); @@ -47,7 +47,7 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { assertEq(strategyAfter.active, false); assertEq(strategyAfter.allocationPoints, 0); assertEq(fourSixTwoSixAgg.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); - assertEq(fourSixTwoSixAgg.withdrawalQueueLength(), withdrawalQueueLengthBefore - 1); + assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore - 1); } function testRemoveStrategy_WithdrawalQueueOrdering() public { diff --git a/test/unit/ReorderWithdrawalQueueTest.t.sol b/test/unit/ReorderWithdrawalQueueTest.t.sol index a68e8341..019ee516 100644 --- a/test/unit/ReorderWithdrawalQueueTest.t.sol +++ b/test/unit/ReorderWithdrawalQueueTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg, IEVault} from "../common/FourSixTwoSixAggBase.t.sol"; +import {FourSixTwoSixAggBase, FourSixTwoSixAgg, IEVault, WithdrawalQueue} from "../common/FourSixTwoSixAggBase.t.sol"; contract ReorderWithdrawalQueueTest is FourSixTwoSixAggBase { uint256 eTSTAllocationPoints = 500e18; @@ -25,37 +25,27 @@ contract ReorderWithdrawalQueueTest is FourSixTwoSixAggBase { } function testReorderWithdrawalQueue() public { - assertEq( - fourSixTwoSixAgg.getStrategy(fourSixTwoSixAgg.withdrawalQueue(0)).allocationPoints, eTSTAllocationPoints - ); - assertEq( - fourSixTwoSixAgg.getStrategy(fourSixTwoSixAgg.withdrawalQueue(1)).allocationPoints, - eTSTsecondaryAllocationPoints - ); + assertEq(fourSixTwoSixAgg.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTAllocationPoints); + assertEq(fourSixTwoSixAgg.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTsecondaryAllocationPoints); vm.prank(manager); - fourSixTwoSixAgg.reorderWithdrawalQueue(0, 1); - - assertEq( - fourSixTwoSixAgg.getStrategy(fourSixTwoSixAgg.withdrawalQueue(0)).allocationPoints, - eTSTsecondaryAllocationPoints - ); - assertEq( - fourSixTwoSixAgg.getStrategy(fourSixTwoSixAgg.withdrawalQueue(1)).allocationPoints, eTSTAllocationPoints - ); + withdrawalQueue.reorderWithdrawalQueue(0, 1); + + assertEq(fourSixTwoSixAgg.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTsecondaryAllocationPoints); + assertEq(fourSixTwoSixAgg.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTAllocationPoints); } function testReorderWithdrawalQueueWhenOutOfBounds() public { vm.startPrank(manager); - vm.expectRevert(FourSixTwoSixAgg.OutOfBounds.selector); - fourSixTwoSixAgg.reorderWithdrawalQueue(0, 3); + vm.expectRevert(WithdrawalQueue.OutOfBounds.selector); + withdrawalQueue.reorderWithdrawalQueue(0, 3); vm.stopPrank(); } function testReorderWithdrawalQueueWhenSameIndex() public { vm.startPrank(manager); - vm.expectRevert(FourSixTwoSixAgg.SameIndexes.selector); - fourSixTwoSixAgg.reorderWithdrawalQueue(1, 1); + vm.expectRevert(WithdrawalQueue.SameIndexes.selector); + withdrawalQueue.reorderWithdrawalQueue(0, 0); vm.stopPrank(); } } From 81f619cc54b1845a39b8f80ba8f1e516c7119257 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 14 Jun 2024 18:02:39 +0300 Subject: [PATCH 166/316] add files --- src/FourSixTwoSixAggFactory.sol | 46 +++++++++++ src/WithdrawalQueue.sol | 123 +++++++++++++++++++++++++++++ src/interface/IWithdrawalQueue.sol | 20 +++++ 3 files changed, 189 insertions(+) create mode 100644 src/FourSixTwoSixAggFactory.sol create mode 100644 src/WithdrawalQueue.sol create mode 100644 src/interface/IWithdrawalQueue.sol diff --git a/src/FourSixTwoSixAggFactory.sol b/src/FourSixTwoSixAggFactory.sol new file mode 100644 index 00000000..5b169b7e --- /dev/null +++ b/src/FourSixTwoSixAggFactory.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {FourSixTwoSixAgg} from "./FourSixTwoSixAgg.sol"; +import {WithdrawalQueue} from "./WithdrawalQueue.sol"; + +contract FourSixTwoSixAggFactory { + address public immutable evc; + address public immutable balanceTracker; + address public immutable rebalancer; + + constructor(address _evc, address _balanceTracker, address _rebalancer) { + evc = _evc; + balanceTracker = _balanceTracker; + rebalancer = _rebalancer; + } + + // TODO: decrease bytecode size, use clones or something + function deployEulerAggregationLayer( + address _asset, + string memory _name, + string memory _symbol, + uint256 _initialCashAllocationPoints, + address[] memory _initialStrategies, + uint256[] memory _initialStrategiesAllocationPoints + ) external returns (address) { + address withdrawalQueueAddr = address(new WithdrawalQueue()); + address fourSixTwoSixAggAddr = address( + new FourSixTwoSixAgg( + evc, + balanceTracker, + withdrawalQueueAddr, + rebalancer, + msg.sender, + _asset, + _name, + _symbol, + _initialCashAllocationPoints, + _initialStrategies, + _initialStrategiesAllocationPoints + ) + ); + + return fourSixTwoSixAggAddr; + } +} diff --git a/src/WithdrawalQueue.sol b/src/WithdrawalQueue.sol new file mode 100644 index 00000000..1ea0c269 --- /dev/null +++ b/src/WithdrawalQueue.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +// external dep +import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumerable.sol"; +import {IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; +// internal dep +import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; + +contract WithdrawalQueue is AccessControlEnumerable { + error OutOfBounds(); + error SameIndexes(); + error NotEnoughAssets(); + + bytes32 public constant WITHDRAW_QUEUE_MANAGER = keccak256("WITHDRAW_QUEUE_MANAGER"); + bytes32 public constant WITHDRAW_QUEUE_MANAGER_ADMIN = keccak256("WITHDRAW_QUEUE_MANAGER_ADMIN"); + + address public eulerAggregationVault; + + /// @dev An array of strategy addresses to withdraw from + address[] public withdrawalQueue; + + bool private isInitialized_; + + event ReorderWithdrawalQueue(uint8 index1, uint8 index2); + + function initWithdrawalQueue(address _owner, address[] calldata _initialStrategies) external { + if (isInitialized_) revert(); + + isInitialized_ = true; + + for (uint256 i; i < _initialStrategies.length; ++i) { + withdrawalQueue.push(_initialStrategies[i]); + } + + eulerAggregationVault = msg.sender; + + // Setup DEFAULT_ADMIN + _grantRole(DEFAULT_ADMIN_ROLE, _owner); + _setRoleAdmin(WITHDRAW_QUEUE_MANAGER, WITHDRAW_QUEUE_MANAGER_ADMIN); + } + + // TODO: add access control + function addStrategyToWithdrawalQueue(address _strategy) external { + withdrawalQueue.push(_strategy); + } + + // TODO: add access control + function removeStrategyFromWithdrawalQueue(address _strategy) external { + uint256 lastStrategyIndex = withdrawalQueue.length - 1; + + for (uint256 i = 0; i < lastStrategyIndex; ++i) { + if (withdrawalQueue[i] == _strategy) { + withdrawalQueue[i] = withdrawalQueue[lastStrategyIndex]; + + break; + } + } + + withdrawalQueue.pop(); + } + + // TODO: add access control + function executeWithdrawFromQueue( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares, + uint256 availableAssets + ) external { + uint256 numStrategies = withdrawalQueue.length; + for (uint256 i; i < numStrategies; ++i) { + IERC4626 strategy = IERC4626(withdrawalQueue[i]); + + IFourSixTwoSixAgg(eulerAggregationVault).harvest(address(strategy)); + + uint256 underlyingBalance = strategy.maxWithdraw(eulerAggregationVault); + uint256 desiredAssets = assets - availableAssets; + uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; + + IFourSixTwoSixAgg(eulerAggregationVault).withdrawFromStrategy(address(strategy), withdrawAmount); + + // update assetsRetrieved + availableAssets += withdrawAmount; + + if (availableAssets >= assets) { + break; + } + } + + if (availableAssets < assets) { + revert NotEnoughAssets(); + } + + IFourSixTwoSixAgg(eulerAggregationVault).executeWithdrawFromReserve(caller, receiver, owner, assets, shares); + } + + /// @notice Swap two strategies indexes in the withdrawal queue. + /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER. + /// @param _index1 index of first strategy + /// @param _index2 index of second strategy + function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external onlyRole(WITHDRAW_QUEUE_MANAGER) { + uint256 length = withdrawalQueue.length; + if (_index1 >= length || _index2 >= length) { + revert OutOfBounds(); + } + + if (_index1 == _index2) { + revert SameIndexes(); + } + + (withdrawalQueue[_index1], withdrawalQueue[_index2]) = (withdrawalQueue[_index2], withdrawalQueue[_index1]); + + emit ReorderWithdrawalQueue(_index1, _index2); + } + + /// @notice Return the withdrawal queue length. + /// @return uint256 length + function withdrawalQueueLength() external view returns (uint256) { + return withdrawalQueue.length; + } +} diff --git a/src/interface/IWithdrawalQueue.sol b/src/interface/IWithdrawalQueue.sol new file mode 100644 index 00000000..d51c0550 --- /dev/null +++ b/src/interface/IWithdrawalQueue.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +interface IWithdrawalQueue { + function initWithdrawalQueue(address _owner, address[] calldata _initialStrategies) external; + function addStrategyToWithdrawalQueue(address _strategy) external; + function removeStrategyFromWithdrawalQueue(address _strategy) external; + function executeWithdrawFromQueue( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares, + uint256 availableAssets + ) external; + function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external; + + function withdrawalQueueLength() external view returns (uint256); + function withdrawalQueue(uint256 _index) external view returns (address); +} From f7886c3358f29ca6220f9a9c2d8f4156edb44931 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:11:47 +0300 Subject: [PATCH 167/316] forge install: openzeppelin-contracts-upgradeable v5.0.2 --- .gitmodules | 3 +++ lib/openzeppelin-contracts-upgradeable | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts-upgradeable diff --git a/.gitmodules b/.gitmodules index ae5ee093..5352aeba 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ [submodule "lib/reward-streams"] path = lib/reward-streams url = https://github.com/euler-xyz/reward-streams +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 00000000..723f8cab --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1 From d0193798a5bccc2b9affeb899f2ebaffce0124b6 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 17 Jun 2024 12:05:11 +0300 Subject: [PATCH 168/316] refactor --- remappings.txt | 3 ++- src/FourSixTwoSixAgg.sol | 16 +++++++++------- src/Rebalancer.sol | 2 +- src/WithdrawalQueue.sol | 6 +++--- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/remappings.txt b/remappings.txt index 1ad598ca..644293a7 100644 --- a/remappings.txt +++ b/remappings.txt @@ -5,4 +5,5 @@ forge-std/=lib/forge-std/src/ evk/=lib/euler-vault-kit/ reward-streams=lib/reward-streams/src openzeppelin-contracts/=lib/reward-streams/lib/openzeppelin-contracts/contracts -@openzeppelin/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ +@openzeppelin/=lib/ethereum-vault-connector/lib/openzeppelin-contracts +@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ \ No newline at end of file diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 8ba1de5c..b0f77ac1 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -2,13 +2,15 @@ pragma solidity ^0.8.0; // external dep -import {Context} from "@openzeppelin/utils/Context.sol"; -import {ERC20, IERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; -import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol"; -import {ERC4626, IERC4626, Math} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; -import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumerable.sol"; -import {SafeCast} from "@openzeppelin/utils/math/SafeCast.sol"; +import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgradeable.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +// import {ERC4626, IERC4626, Math} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; +// import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumerable.sol"; +import {ERC4626Upgradeable, IERC4626, ERC20Upgradeable, Math} from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; // internal dep import {Hooks} from "./Hooks.sol"; @@ -20,7 +22,7 @@ import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; /// @dev Do NOT use with rebasing tokens /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol /// @dev inspired by Yearn v3 ❤️ -contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC4626, AccessControlEnumerable, Hooks { +contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC4626Upgradeable, AccessControlEnumerableUpgradeable, Hooks { using SafeERC20 for IERC20; using SafeCast for uint256; diff --git a/src/Rebalancer.sol b/src/Rebalancer.sol index 089ba335..6c5f554a 100644 --- a/src/Rebalancer.sol +++ b/src/Rebalancer.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; -import {IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; +import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; contract Rebalancer { event ExecuteRebalance( diff --git a/src/WithdrawalQueue.sol b/src/WithdrawalQueue.sol index 1ea0c269..d2f5ac7e 100644 --- a/src/WithdrawalQueue.sol +++ b/src/WithdrawalQueue.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.0; // external dep -import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumerable.sol"; -import {IERC4626} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; +import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; +import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; // internal dep import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; -contract WithdrawalQueue is AccessControlEnumerable { +contract WithdrawalQueue is AccessControlEnumerableUpgradeable { error OutOfBounds(); error SameIndexes(); error NotEnoughAssets(); From 9ebd543469d5954ff0af2b89c94009575eae66fa Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 17 Jun 2024 12:05:25 +0300 Subject: [PATCH 169/316] forge install: openzeppelin-contracts v5.0.2 --- .gitmodules | 3 +++ lib/openzeppelin-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index 5352aeba..02ccc320 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 00000000..dbb6104c --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 From 0d8133ceb13d61cddfb496745dda6f7f812f4640 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:10:21 +0300 Subject: [PATCH 170/316] unstructured storage --- remappings.txt | 2 +- src/BalanceForwarder.sol | 65 +++- src/FourSixTwoSixAgg.sol | 403 +++++++++++++++++-------- src/FourSixTwoSixAggFactory.sol | 30 +- src/Hooks.sol | 30 +- src/WithdrawalQueue.sol | 78 +++-- src/interface/IWithdrawalQueue.sol | 2 +- test/common/FourSixTwoSixAggBase.t.sol | 2 +- test/e2e/BalanceForwarderE2ETest.t.sol | 12 +- test/e2e/PerformanceFeeE2ETest.t.sol | 13 +- test/unit/GulpTest.t.sol | 12 +- 11 files changed, 440 insertions(+), 209 deletions(-) diff --git a/remappings.txt b/remappings.txt index 644293a7..4d63589c 100644 --- a/remappings.txt +++ b/remappings.txt @@ -5,5 +5,5 @@ forge-std/=lib/forge-std/src/ evk/=lib/euler-vault-kit/ reward-streams=lib/reward-streams/src openzeppelin-contracts/=lib/reward-streams/lib/openzeppelin-contracts/contracts -@openzeppelin/=lib/ethereum-vault-connector/lib/openzeppelin-contracts +@openzeppelin/=lib/openzeppelin-contracts/ @openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ \ No newline at end of file diff --git a/src/BalanceForwarder.sol b/src/BalanceForwarder.sol index 1ceac9c6..8e082fba 100644 --- a/src/BalanceForwarder.sol +++ b/src/BalanceForwarder.sol @@ -13,16 +13,28 @@ abstract contract BalanceForwarder is IBalanceForwarder { error AlreadyEnabled(); error AlreadyDisabled(); - IBalanceTracker public immutable balanceTracker; + /// @custom:storage-location erc7201:euler_aggregation_vault.storage.BalanceForwarder + struct BalanceForwarderStorage { + IBalanceTracker balanceTracker; + mapping(address => bool) isBalanceForwarderEnabled; + } + // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.BalanceForwarder")) - 1)) & ~bytes32(uint256(0xff)) + + bytes32 private constant BalanceForwarderStorageLocation = + 0xf0af7cc3986d6cac468ac54c07f5f08359b3a00d65f8e2a86f2676ec79073f00; - mapping(address => bool) internal isBalanceForwarderEnabled; + function _getBalanceForwarderStorage() private pure returns (BalanceForwarderStorage storage $) { + assembly { + $.slot := BalanceForwarderStorageLocation + } + } event EnableBalanceForwarder(address indexed _user); event DisableBalanceForwarder(address indexed _user); - constructor(address _balanceTracker) { - balanceTracker = IBalanceTracker(_balanceTracker); - } + // constructor(address _balanceTracker) { + // balanceTracker = IBalanceTracker(_balanceTracker); + // } /// @notice Enables balance forwarding for the authenticated account /// @dev Only the authenticated account can enable balance forwarding for itself @@ -37,22 +49,26 @@ abstract contract BalanceForwarder is IBalanceForwarder { /// @notice Retrieve the address of rewards contract, tracking changes in account's balances /// @return The balance tracker address function balanceTrackerAddress() external view returns (address) { - return address(balanceTracker); + BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); + + return address($.balanceTracker); } /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract /// @param _account Address to query /// @return True if balance forwarder is enabled function balanceForwarderEnabled(address _account) external view returns (bool) { - return isBalanceForwarderEnabled[_account]; + return _balanceForwarderEnabled(_account); } function _enableBalanceForwarder(address _sender, uint256 _senderBalance) internal { - if (address(balanceTracker) == address(0)) revert NotSupported(); - if (isBalanceForwarderEnabled[_sender]) revert AlreadyEnabled(); + BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); + + if (address($.balanceTracker) == address(0)) revert NotSupported(); + if ($.isBalanceForwarderEnabled[_sender]) revert AlreadyEnabled(); - isBalanceForwarderEnabled[_sender] = true; - balanceTracker.balanceTrackerHook(_sender, _senderBalance, false); + $.isBalanceForwarderEnabled[_sender] = true; + $.balanceTracker.balanceTrackerHook(_sender, _senderBalance, false); emit EnableBalanceForwarder(_sender); } @@ -61,12 +77,31 @@ abstract contract BalanceForwarder is IBalanceForwarder { /// @dev Only the authenticated account can disable balance forwarding for itself /// @dev Should call the IBalanceTracker hook with the account's balance of 0 function _disableBalanceForwarder(address _sender) internal { - if (address(balanceTracker) == address(0)) revert NotSupported(); - if (!isBalanceForwarderEnabled[_sender]) revert AlreadyDisabled(); + BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); - isBalanceForwarderEnabled[_sender] = false; - balanceTracker.balanceTrackerHook(_sender, 0, false); + if (address($.balanceTracker) == address(0)) revert NotSupported(); + if (!$.isBalanceForwarderEnabled[_sender]) revert AlreadyDisabled(); + + $.isBalanceForwarderEnabled[_sender] = false; + $.balanceTracker.balanceTrackerHook(_sender, 0, false); emit DisableBalanceForwarder(_sender); } + + /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract + /// @param _account Address to query + /// @return True if balance forwarder is enabled + function _balanceForwarderEnabled(address _account) internal view returns (bool) { + BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); + + return $.isBalanceForwarderEnabled[_account]; + } + + /// @notice Retrieve the instance of rewards contract, tracking changes in account's balances + /// @return The balance tracker contract + function _balanceTracker() internal view returns (IBalanceTracker) { + BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); + + return $.balanceTracker; + } } diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index b0f77ac1..f8f30447 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -4,17 +4,21 @@ pragma solidity ^0.8.0; // external dep import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgradeable.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -// import {ERC4626, IERC4626, Math} from "@openzeppelin/token/ERC20/extensions/ERC4626.sol"; -// import {AccessControlEnumerable} from "@openzeppelin/access/AccessControlEnumerable.sol"; -import {ERC4626Upgradeable, IERC4626, ERC20Upgradeable, Math} from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; -import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; +import { + ERC4626Upgradeable, + IERC4626, + ERC20Upgradeable, + Math +} from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import {AccessControlEnumerableUpgradeable} from + "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; +import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; // internal dep import {Hooks} from "./Hooks.sol"; -import {BalanceForwarder, IBalanceForwarder} from "./BalanceForwarder.sol"; +import {BalanceForwarder, IBalanceForwarder, IBalanceTracker} from "./BalanceForwarder.sol"; import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; @@ -22,7 +26,13 @@ import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; /// @dev Do NOT use with rebasing tokens /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol /// @dev inspired by Yearn v3 ❤️ -contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC4626Upgradeable, AccessControlEnumerableUpgradeable, Hooks { +contract FourSixTwoSixAgg is + ERC4626Upgradeable, + AccessControlEnumerableUpgradeable, + BalanceForwarder, + Hooks, + IFourSixTwoSixAgg +{ using SafeERC20 for IERC20; using SafeCast for uint256; @@ -60,37 +70,62 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; uint256 public constant INTEREST_SMEAR = 2 weeks; - /// @dev store the interest rate smearing params - ESR internal esrSlot; - - /// @dev Total amount of _asset deposited into FourSixTwoSixAgg contract - uint256 public totalAssetsDeposited; - /// @dev Total amount of _asset deposited across all strategies. - uint256 public totalAllocated; - /// @dev Total amount of allocation points across all strategies including the cash reserve. - uint256 public totalAllocationPoints; - /// @dev fee rate - uint256 public performanceFee; - /// @dev fee recipient address - address public feeRecipient; - - address public withdrawalQueue; - - /// @dev Mapping between strategy address and it's allocation config - mapping(address => Strategy) internal strategies; + /// @custom:storage-location erc7201:euler_aggregation_vault.storage.AggregationVault + struct AggregationVaultStorage { + IEVC evc; + /// @dev Total amount of _asset deposited into FourSixTwoSixAgg contract + uint256 totalAssetsDeposited; + /// @dev Total amount of _asset deposited across all strategies. + uint256 totalAllocated; + /// @dev Total amount of allocation points across all strategies including the cash reserve. + uint256 totalAllocationPoints; + /// @dev fee rate + uint256 performanceFee; + /// @dev fee recipient address + address feeRecipient; + /// @dev Withdrawal queue contract's address + address withdrawalQueue; + /// @dev Mapping between strategy address and it's allocation config + mapping(address => Strategy) strategies; + } + + // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.AggregationVault")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant AggregationVaultStorageLocation = + 0x7da5ece5aff94f3377324d715703a012d3253da37511270103c646f171c0aa00; + + function _getAggregationVaultStorage() private pure returns (AggregationVaultStorage storage $) { + assembly { + $.slot := AggregationVaultStorageLocation + } + } /// @dev Euler saving rate struct /// lastInterestUpdate: last timestamo where interest was updated. /// interestSmearEnd: timestamp when the smearing of interest end. /// interestLeft: amount of interest left to smear. /// locked: if locked or not for update. - struct ESR { + /// @custom:storage-location erc7201:euler_aggregation_vault.storage.AggregationVaultSavingRate + struct AggregationVaultSavingRateStorage { uint40 lastInterestUpdate; uint40 interestSmearEnd; uint168 interestLeft; uint8 locked; } + // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.AggregationVaultSavingRate")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant AggregationVaultSavingRateStorageLocation = + 0xdb2394914f0f6fb18662eb905f211539008437f354f711cd6b8106daf3f40500; + + function _getAggregationVaultSavingRateStorage() + private + pure + returns (AggregationVaultSavingRateStorage storage $) + { + assembly { + $.slot := AggregationVaultSavingRateStorageLocation + } + } + event SetFeeRecipient(address indexed oldRecipient, address indexed newRecipient); event SetPerformanceFee(uint256 oldFee, uint256 newFee); event OptInStrategyRewards(address indexed strategy); @@ -106,22 +141,37 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev Non reentrancy modifier for interest rate updates modifier nonReentrant() { - if (esrSlot.locked == REENTRANCYLOCK__LOCKED) revert Reentrancy(); + AggregationVaultSavingRateStorage memory avsrCache = _getAggregationVaultSavingRateStorage(); - esrSlot.locked = REENTRANCYLOCK__LOCKED; + if (avsrCache.locked == REENTRANCYLOCK__LOCKED) revert Reentrancy(); + + avsrCache.locked = REENTRANCYLOCK__LOCKED; _; - esrSlot.locked = REENTRANCYLOCK__UNLOCKED; - } - - /// @dev Constructor - /// @param _evc EVC address - /// @param _asset Aggregator's asset address - /// @param _name Aggregator's name - /// @param _symbol Aggregator's symbol - /// @param _initialCashAllocationPoints Initial points to be allocated to the cash reserve - /// @param _initialStrategies An array of initial strategies addresses - /// @param _initialStrategiesAllocationPoints An array of initial strategies allocation points - constructor( + avsrCache.locked = REENTRANCYLOCK__UNLOCKED; + } + + // /// @dev Constructor + // /// @param _evc EVC address + // /// @param _asset Aggregator's asset address + // /// @param _name Aggregator's name + // /// @param _symbol Aggregator's symbol + // /// @param _initialCashAllocationPoints Initial points to be allocated to the cash reserve + // /// @param _initialStrategies An array of initial strategies addresses + // /// @param _initialStrategiesAllocationPoints An array of initial strategies allocation points + + function _lock() private onlyInitializing { + AggregationVaultSavingRateStorage storage $ = _getAggregationVaultSavingRateStorage(); + + $.locked = REENTRANCYLOCK__UNLOCKED; + } + + function _setWithdrawalQueue(address _withdrawalQueue) private { + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + $.withdrawalQueue = _withdrawalQueue; + } + + function init( address _evc, address _balanceTracker, address _withdrawalQueue, @@ -133,15 +183,20 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 uint256 _initialCashAllocationPoints, address[] memory _initialStrategies, uint256[] memory _initialStrategiesAllocationPoints - ) BalanceForwarder(_balanceTracker) EVCUtil(_evc) ERC4626(IERC20(_asset)) ERC20(_name, _symbol) { - esrSlot.locked = REENTRANCYLOCK__UNLOCKED; + ) external initializer { + __ERC4626_init_unchained(IERC20(_asset)); + __ERC20_init_unchained(_name, _symbol); + + _lock(); if (_initialStrategies.length != _initialStrategiesAllocationPoints.length) revert ArrayLengthMismatch(); if (_initialCashAllocationPoints == 0) revert InitialAllocationPointsZero(); - withdrawalQueue = _withdrawalQueue; + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + $.withdrawalQueue = _withdrawalQueue; - strategies[address(0)] = + $.strategies[address(0)] = Strategy({allocated: 0, allocationPoints: _initialCashAllocationPoints.toUint120(), active: true, cap: 0}); uint256 cachedTotalAllocationPoints = _initialCashAllocationPoints; @@ -151,9 +206,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 revert InvalidStrategyAsset(); } - if (strategies[_initialStrategies[i]].active) revert DuplicateInitialStrategy(); + if ($.strategies[_initialStrategies[i]].active) revert DuplicateInitialStrategy(); - strategies[_initialStrategies[i]] = Strategy({ + $.strategies[_initialStrategies[i]] = Strategy({ allocated: 0, allocationPoints: _initialStrategiesAllocationPoints[i].toUint120(), active: true, @@ -162,8 +217,8 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 cachedTotalAllocationPoints += _initialStrategiesAllocationPoints[i]; } - totalAllocationPoints = cachedTotalAllocationPoints; - IWithdrawalQueue(withdrawalQueue).initWithdrawalQueue(_owner, _initialStrategies); + $.totalAllocationPoints = cachedTotalAllocationPoints; + IWithdrawalQueue($.withdrawalQueue).init(_owner, _initialStrategies); // Setup DEFAULT_ADMIN _grantRole(DEFAULT_ADMIN_ROLE, _owner); @@ -182,29 +237,35 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice Set performance fee recipient address /// @notice @param _newFeeRecipient Recipient address function setFeeRecipient(address _newFeeRecipient) external onlyRole(MANAGER) { - if (_newFeeRecipient == feeRecipient) revert FeeRecipientAlreadySet(); + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); - emit SetFeeRecipient(feeRecipient, _newFeeRecipient); + if (_newFeeRecipient == $.feeRecipient) revert FeeRecipientAlreadySet(); - feeRecipient = _newFeeRecipient; + emit SetFeeRecipient($.feeRecipient, _newFeeRecipient); + + $.feeRecipient = _newFeeRecipient; } /// @notice Set performance fee (1e18 == 100%) /// @notice @param _newFee Fee rate function setPerformanceFee(uint256 _newFee) external onlyRole(MANAGER) { + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + if (_newFee > MAX_PERFORMANCE_FEE) revert MaxPerformanceFeeExceeded(); - if (feeRecipient == address(0)) revert FeeRecipientNotSet(); - if (_newFee == performanceFee) revert PerformanceFeeAlreadySet(); + if ($.feeRecipient == address(0)) revert FeeRecipientNotSet(); + if (_newFee == $.performanceFee) revert PerformanceFeeAlreadySet(); - emit SetPerformanceFee(performanceFee, _newFee); + emit SetPerformanceFee($.performanceFee, _newFee); - performanceFee = _newFee; + $.performanceFee = _newFee; } /// @notice Opt in to strategy rewards /// @param _strategy Strategy address function optInStrategyRewards(address _strategy) external onlyRole(MANAGER) nonReentrant { - if (!strategies[_strategy].active) revert InactiveStrategy(); + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + if (!$.strategies[_strategy].active) revert InactiveStrategy(); IBalanceForwarder(_strategy).enableBalanceForwarder(); @@ -272,18 +333,20 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 nonReentrant onlyRole(REBALANCER) { - Strategy memory strategyData = strategies[_strategy]; + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + Strategy memory strategyData = $.strategies[_strategy]; if (_isDeposit) { // Do required approval (safely) and deposit IERC20(asset()).safeIncreaseAllowance(_strategy, _amountToRebalance); IERC4626(_strategy).deposit(_amountToRebalance, address(this)); - strategies[_strategy].allocated = uint120(strategyData.allocated + _amountToRebalance); - totalAllocated += _amountToRebalance; + $.strategies[_strategy].allocated = uint120(strategyData.allocated + _amountToRebalance); + $.totalAllocated += _amountToRebalance; } else { IERC4626(_strategy).withdraw(_amountToRebalance, address(this), address(this)); - strategies[_strategy].allocated = (strategyData.allocated - _amountToRebalance).toUint120(); - totalAllocated -= _amountToRebalance; + $.strategies[_strategy].allocated = (strategyData.allocated - _amountToRebalance).toUint120(); + $.totalAllocated -= _amountToRebalance; } emit Rebalance(_strategy, _amountToRebalance, _isDeposit); @@ -298,14 +361,16 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 nonReentrant onlyRole(STRATEGY_MANAGER) { - Strategy memory strategyDataCache = strategies[_strategy]; + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + Strategy memory strategyDataCache = $.strategies[_strategy]; if (!strategyDataCache.active) { revert InactiveStrategy(); } - strategies[_strategy].allocationPoints = _newPoints.toUint120(); - totalAllocationPoints = totalAllocationPoints + _newPoints - strategyDataCache.allocationPoints; + $.strategies[_strategy].allocationPoints = _newPoints.toUint120(); + $.totalAllocationPoints = $.totalAllocationPoints + _newPoints - strategyDataCache.allocationPoints; emit AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); } @@ -315,11 +380,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @param _strategy Strategy address. /// @param _cap Cap amount function setStrategyCap(address _strategy, uint256 _cap) external nonReentrant onlyRole(STRATEGY_MANAGER) { - if (!strategies[_strategy].active) { + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + if (!$.strategies[_strategy].active) { revert InactiveStrategy(); } - strategies[_strategy].cap = _cap.toUint120(); + $.strategies[_strategy].cap = _cap.toUint120(); emit SetStrategyCap(_strategy, _cap); } @@ -329,7 +396,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @param _strategy Address of the strategy /// @param _allocationPoints Strategy's allocation points function addStrategy(address _strategy, uint256 _allocationPoints) external nonReentrant onlyRole(STRATEGY_ADDER) { - if (strategies[_strategy].active) { + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + if ($.strategies[_strategy].active) { revert StrategyAlreadyExist(); } @@ -339,11 +408,11 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 _callHooksTarget(ADD_STRATEGY, _msgSender()); - strategies[_strategy] = + $.strategies[_strategy] = Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); - totalAllocationPoints += _allocationPoints; - IWithdrawalQueue(withdrawalQueue).addStrategyToWithdrawalQueue(_strategy); + $.totalAllocationPoints += _allocationPoints; + IWithdrawalQueue($.withdrawalQueue).addStrategyToWithdrawalQueue(_strategy); emit AddStrategy(_strategy, _allocationPoints); } @@ -355,7 +424,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 function removeStrategy(address _strategy) external nonReentrant onlyRole(STRATEGY_REMOVER) { if (_strategy == address(0)) revert CanNotRemoveCashReserve(); - Strategy storage strategyStorage = strategies[_strategy]; + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + Strategy storage strategyStorage = $.strategies[_strategy]; if (!strategyStorage.active) { revert AlreadyRemoved(); @@ -363,19 +434,18 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 _callHooksTarget(REMOVE_STRATEGY, _msgSender()); - totalAllocationPoints -= strategyStorage.allocationPoints; + $.totalAllocationPoints -= strategyStorage.allocationPoints; strategyStorage.active = false; strategyStorage.allocationPoints = 0; // remove from withdrawalQueue - IWithdrawalQueue(withdrawalQueue).removeStrategyFromWithdrawalQueue(_strategy); + IWithdrawalQueue($.withdrawalQueue).removeStrategyFromWithdrawalQueue(_strategy); emit RemoveStrategy(_strategy); } /// @notice update accrued interest - /// @return struct ESR struct - function updateInterestAccrued() external returns (ESR memory) { + function updateInterestAccrued() external returns (AggregationVaultSavingRateStorage memory) { return _updateInterestAccrued(); } @@ -388,19 +458,55 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @param _strategy strategy's address /// @return Strategy struct function getStrategy(address _strategy) external view returns (Strategy memory) { - return strategies[_strategy]; + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + return $.strategies[_strategy]; } /// @notice Return the ESR struct /// @return ESR struct - function getESRSlot() external view returns (ESR memory) { - return esrSlot; + function getAggregationVaultSavingRate() external view returns (AggregationVaultSavingRateStorage memory) { + AggregationVaultSavingRateStorage memory avsrCache = _getAggregationVaultSavingRateStorage(); + + return avsrCache; } /// @notice Return the accrued interest /// @return uint256 accrued interest function interestAccrued() external view returns (uint256) { - return _interestAccruedFromCache(esrSlot); + AggregationVaultSavingRateStorage memory avsrCache = _getAggregationVaultSavingRateStorage(); + + return _interestAccruedFromCache(avsrCache); + } + + function totalAllocated() external view returns (uint256) { + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + return $.totalAllocated; + } + + function totalAllocationPoints() external view returns (uint256) { + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + return $.totalAllocationPoints; + } + + function totalAssetsDeposited() external view returns (uint256) { + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + return $.totalAssetsDeposited; + } + + function withdrawalQueue() external view returns (address) { + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + return $.withdrawalQueue; + } + + function performanceFeeConfig() external view returns (address, uint256) { + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + return ($.feeRecipient, $.performanceFee); } /// @dev See {IERC4626-deposit}. @@ -449,31 +555,49 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @notice update accrued interest. /// @return struct ESR struct. - function _updateInterestAccrued() internal returns (ESR memory) { - ESR memory esrSlotCache = esrSlot; - uint256 accruedInterest = _interestAccruedFromCache(esrSlotCache); + function _updateInterestAccrued() internal returns (AggregationVaultSavingRateStorage memory) { + AggregationVaultSavingRateStorage memory avsrCache = _getAggregationVaultSavingRateStorage(); + + uint256 accruedInterest = _interestAccruedFromCache(avsrCache); + // it's safe to down-cast because the accrued interest is a fraction of interest left - esrSlotCache.interestLeft -= uint168(accruedInterest); - esrSlotCache.lastInterestUpdate = uint40(block.timestamp); + avsrCache.interestLeft -= uint168(accruedInterest); + avsrCache.lastInterestUpdate = uint40(block.timestamp); + // write esrSlotCache back to storage in a single SSTORE - esrSlot = esrSlotCache; + _setAggregationVaultSavingRateStorage(avsrCache); + // Move interest accrued to totalAssetsDeposited - totalAssetsDeposited += accruedInterest; + _increaseTotalAssetsDepositedByInterest(accruedInterest); - return esrSlotCache; + return avsrCache; + } + + function _setAggregationVaultSavingRateStorage(AggregationVaultSavingRateStorage memory _avsrCache) private { + AggregationVaultSavingRateStorage storage $ = _getAggregationVaultSavingRateStorage(); + + $.lastInterestUpdate = _avsrCache.lastInterestUpdate; + $.interestSmearEnd = _avsrCache.interestSmearEnd; + $.interestLeft = _avsrCache.interestLeft; + $.locked = _avsrCache.locked; } /// @notice Return the total amount of assets deposited, plus the accrued interest. /// @return uint256 total amount function totalAssets() public view override returns (uint256) { - return totalAssetsDeposited + _interestAccruedFromCache(esrSlot); + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultSavingRateStorage memory avsrCache = _getAggregationVaultSavingRateStorage(); + + return $.totalAssetsDeposited + _interestAccruedFromCache(avsrCache); } /// @notice get the total assets allocatable /// @dev the total assets allocatable is the amount of assets deposited into the aggregator + assets already deposited into strategies /// @return uint256 total assets function totalAssetsAllocatable() public view returns (uint256) { - return IERC20(asset()).balanceOf(address(this)) + totalAllocated; + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + return IERC20(asset()).balanceOf(address(this)) + $.totalAllocated; } /// @dev Increate the total assets deposited, and call IERC4626._deposit() @@ -481,7 +605,9 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { _callHooksTarget(DEPOSIT, caller); - totalAssetsDeposited += assets; + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + $.totalAssetsDeposited += assets; super._deposit(caller, receiver, assets, shares); } @@ -496,11 +622,13 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 { _callHooksTarget(WITHDRAW, caller); - totalAssetsDeposited -= assets; + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + $.totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); if (assetsRetrieved < assets) { - IWithdrawalQueue(withdrawalQueue).executeWithdrawFromQueue( + IWithdrawalQueue($.withdrawalQueue).executeWithdrawFromQueue( caller, receiver, owner, assets, shares, assetsRetrieved ); } else { @@ -510,9 +638,11 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 // TODO: add access control function withdrawFromStrategy(address _strategy, uint256 _withdrawAmount) external { + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + // Update allocated assets - strategies[_strategy].allocated -= uint120(_withdrawAmount); - totalAllocated -= _withdrawAmount; + $.strategies[_strategy].allocated -= uint120(_withdrawAmount); + $.totalAllocated -= _withdrawAmount; // Do actual withdraw from strategy IERC4626(_strategy).withdraw(_withdrawAmount, address(this), address(this)); @@ -540,27 +670,31 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev gulp positive yield and increment the left interest function _gulp() internal { - ESR memory esrSlotCache = _updateInterestAccrued(); + AggregationVaultSavingRateStorage memory avsrCache = _updateInterestAccrued(); + + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); - if (totalAssetsDeposited == 0) return; - uint256 toGulp = totalAssetsAllocatable() - totalAssetsDeposited - esrSlotCache.interestLeft; + if ($.totalAssetsDeposited == 0) return; + uint256 toGulp = totalAssetsAllocatable() - $.totalAssetsDeposited - avsrCache.interestLeft; if (toGulp == 0) return; - uint256 maxGulp = type(uint168).max - esrSlotCache.interestLeft; + uint256 maxGulp = type(uint168).max - avsrCache.interestLeft; if (toGulp > maxGulp) toGulp = maxGulp; // cap interest, allowing the vault to function - esrSlotCache.interestSmearEnd = uint40(block.timestamp + INTEREST_SMEAR); - esrSlotCache.interestLeft += uint168(toGulp); // toGulp <= maxGulp <= max uint168 + avsrCache.interestSmearEnd = uint40(block.timestamp + INTEREST_SMEAR); + avsrCache.interestLeft += uint168(toGulp); // toGulp <= maxGulp <= max uint168 - // write esrSlotCache back to storage in a single SSTORE - esrSlot = esrSlotCache; + // write avsrCache back to storage in a single SSTORE + _setAggregationVaultSavingRateStorage(avsrCache); - emit Gulp(esrSlotCache.interestLeft, esrSlotCache.interestSmearEnd); + emit Gulp(avsrCache.interestLeft, avsrCache.interestSmearEnd); } function _harvest(address _strategy) internal { - uint120 strategyAllocatedAmount = strategies[_strategy].allocated; + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + uint120 strategyAllocatedAmount = $.strategies[_strategy].allocated; if (strategyAllocatedAmount == 0) return; @@ -578,35 +712,37 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 yield -= accruedPerformanceFee; } - strategies[_strategy].allocated = uint120(underlyingBalance); - totalAllocated += yield; + $.strategies[_strategy].allocated = uint120(underlyingBalance); + $.totalAllocated += yield; } else { uint256 loss = strategyAllocatedAmount - underlyingBalance; - strategies[_strategy].allocated = uint120(underlyingBalance); - totalAllocated -= loss; + $.strategies[_strategy].allocated = uint120(underlyingBalance); + $.totalAllocated -= loss; - ESR memory esrSlotCache = esrSlot; - if (esrSlotCache.interestLeft >= loss) { - esrSlotCache.interestLeft -= uint168(loss); + AggregationVaultSavingRateStorage memory avsrCache = _getAggregationVaultSavingRateStorage(); + if (avsrCache.interestLeft >= loss) { + avsrCache.interestLeft -= uint168(loss); } else { - totalAssetsDeposited -= loss - esrSlotCache.interestLeft; - esrSlotCache.interestLeft = 0; + $.totalAssetsDeposited -= loss - avsrCache.interestLeft; + avsrCache.interestLeft = 0; } - esrSlot = esrSlotCache; + _setAggregationVaultSavingRateStorage(avsrCache); } emit Harvest(_strategy, underlyingBalance, strategyAllocatedAmount); } function _accruePerformanceFee(address _strategy, uint256 _yield) internal returns (uint120) { - address cachedFeeRecipient = feeRecipient; - uint256 cachedPerformanceFee = performanceFee; + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + address cachedFeeRecipient = $.feeRecipient; + uint256 cachedPerformanceFee = $.performanceFee; if (cachedFeeRecipient == address(0) || cachedPerformanceFee == 0) return 0; // `feeAssets` will be rounded down to 0 if `yield * performanceFee < 1e18`. - uint256 feeAssets = Math.mulDiv(_yield, cachedPerformanceFee, 1e18, Math.Rounding.Down); + uint256 feeAssets = Math.mulDiv(_yield, cachedPerformanceFee, 1e18, Math.Rounding.Floor); if (feeAssets > 0) { IERC4626(_strategy).withdraw(feeAssets, cachedFeeRecipient, address(this)); @@ -621,14 +757,18 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev Calling .balanceTrackerHook() passing the address total balance /// @param from Address sending the amount /// @param to Address receiving the amount - function _afterTokenTransfer(address from, address to, uint256 /*amount*/ ) internal override { + function _update(address from, address to, uint256 value) internal override { + super._update(from, to, value); + if (from == to) return; - if ((from != address(0)) && (isBalanceForwarderEnabled[from])) { + IBalanceTracker balanceTracker = _balanceTracker(); + + if ((from != address(0)) && (_balanceForwarderEnabled(from))) { balanceTracker.balanceTrackerHook(from, super.balanceOf(from), false); } - if ((to != address(0)) && (isBalanceForwarderEnabled[to])) { + if ((to != address(0)) && (_balanceForwarderEnabled(to))) { balanceTracker.balanceTrackerHook(to, super.balanceOf(to), false); } } @@ -636,7 +776,11 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev Get accrued interest without updating it. /// @param esrSlotCache Cached esrSlot /// @return uint256 accrued interest - function _interestAccruedFromCache(ESR memory esrSlotCache) internal view returns (uint256) { + function _interestAccruedFromCache(AggregationVaultSavingRateStorage memory esrSlotCache) + internal + view + returns (uint256) + { // If distribution ended, full amount is accrued if (block.timestamp >= esrSlotCache.interestSmearEnd) { return esrSlotCache.interestLeft; @@ -658,7 +802,20 @@ contract FourSixTwoSixAgg is IFourSixTwoSixAgg, BalanceForwarder, EVCUtil, ERC46 /// @dev This function returns the account on behalf of which the current operation is being performed, which is /// either msg.sender or the account authenticated by the EVC. /// @return The address of the message sender. - function _msgSender() internal view override (Context, EVCUtil) returns (address) { - return EVCUtil._msgSender(); + function _msgSender() internal view override (ContextUpgradeable) returns (address) { + address sender = msg.sender; + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + if (sender == address($.evc)) { + (sender,) = $.evc.getCurrentOnBehalfOfAccount(address(0)); + } + + return sender; + } + + function _increaseTotalAssetsDepositedByInterest(uint256 _accruedInterest) private { + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + + $.totalAssetsDeposited += _accruedInterest; } } diff --git a/src/FourSixTwoSixAggFactory.sol b/src/FourSixTwoSixAggFactory.sol index 5b169b7e..ea27f454 100644 --- a/src/FourSixTwoSixAggFactory.sol +++ b/src/FourSixTwoSixAggFactory.sol @@ -25,22 +25,22 @@ contract FourSixTwoSixAggFactory { uint256[] memory _initialStrategiesAllocationPoints ) external returns (address) { address withdrawalQueueAddr = address(new WithdrawalQueue()); - address fourSixTwoSixAggAddr = address( - new FourSixTwoSixAgg( - evc, - balanceTracker, - withdrawalQueueAddr, - rebalancer, - msg.sender, - _asset, - _name, - _symbol, - _initialCashAllocationPoints, - _initialStrategies, - _initialStrategiesAllocationPoints - ) + FourSixTwoSixAgg fourSixTwoSixAggAddr = new FourSixTwoSixAgg(); + + fourSixTwoSixAggAddr.init( + evc, + balanceTracker, + withdrawalQueueAddr, + rebalancer, + msg.sender, + _asset, + _name, + _symbol, + _initialCashAllocationPoints, + _initialStrategies, + _initialStrategiesAllocationPoints ); - return fourSixTwoSixAggAddr; + return address(fourSixTwoSixAggAddr); } } diff --git a/src/Hooks.sol b/src/Hooks.sol index 8127a6fc..033916cf 100644 --- a/src/Hooks.sol +++ b/src/Hooks.sol @@ -16,12 +16,23 @@ abstract contract Hooks { uint32 public constant WITHDRAW = 1 << 1; uint32 public constant ADD_STRATEGY = 1 << 2; uint32 public constant REMOVE_STRATEGY = 1 << 3; - uint32 constant ACTIONS_COUNTER = 1 << 4; - uint256 public constant HOOKS_MASK = 0x00000000000000000000000000000000000000000000000000000000FFFFFFFF; - /// @dev storing the hooks target and kooked functions. - uint256 hooksConfig; + uint256 constant HOOKS_MASK = 0x00000000000000000000000000000000000000000000000000000000FFFFFFFF; + + struct HooksStorage { + /// @dev storing the hooks target and kooked functions. + uint256 hooksConfig; + } + + // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.Hooks")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant HooksStorageLocation = 0x7daefa3ee1567d8892b825e51ce683a058a5785193bcc2ca50940db02ccbf700; + + function _getHooksStorage() private pure returns (HooksStorage storage $) { + assembly { + $.slot := HooksStorageLocation + } + } event SetHooksConfig(address indexed hooksTarget, uint32 hookedFns); @@ -29,7 +40,9 @@ abstract contract Hooks { /// @return address Hooks contract. /// @return uint32 Hooked functions. function getHooksConfig() external view returns (address, uint32) { - return _getHooksConfig(hooksConfig); + HooksStorage storage $ = _getHooksStorage(); + + return _getHooksConfig($.hooksConfig); } /// @notice Set hooks contract and hooked functions. @@ -52,7 +65,8 @@ abstract contract Hooks { } if (_hookedFns >= ACTIONS_COUNTER) revert InvalidHookedFns(); - hooksConfig = (uint256(uint160(_hooksTarget)) << 32) | uint256(_hookedFns); + HooksStorage storage $ = _getHooksStorage(); + $.hooksConfig = (uint256(uint160(_hooksTarget)) << 32) | uint256(_hookedFns); emit SetHooksConfig(_hooksTarget, _hookedFns); } @@ -61,7 +75,9 @@ abstract contract Hooks { /// @param _fn Function to call the hook for. /// @param _caller Caller's address. function _callHooksTarget(uint32 _fn, address _caller) internal { - (address target, uint32 hookedFns) = _getHooksConfig(hooksConfig); + HooksStorage storage $ = _getHooksStorage(); + + (address target, uint32 hookedFns) = _getHooksConfig($.hooksConfig); if (hookedFns.isNotSet(_fn)) return; diff --git a/src/WithdrawalQueue.sol b/src/WithdrawalQueue.sol index d2f5ac7e..caf7dae1 100644 --- a/src/WithdrawalQueue.sol +++ b/src/WithdrawalQueue.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.0; // external dep -import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; +import {AccessControlEnumerableUpgradeable} from + "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; // internal dep import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; @@ -15,49 +16,66 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { bytes32 public constant WITHDRAW_QUEUE_MANAGER = keccak256("WITHDRAW_QUEUE_MANAGER"); bytes32 public constant WITHDRAW_QUEUE_MANAGER_ADMIN = keccak256("WITHDRAW_QUEUE_MANAGER_ADMIN"); - address public eulerAggregationVault; + struct WithdrawalQueueStorage { + address eulerAggregationVault; + /// @dev An array of strategy addresses to withdraw from + address[] withdrawalQueue; + } - /// @dev An array of strategy addresses to withdraw from - address[] public withdrawalQueue; + // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.WithdrawalQueue")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant WithdrawalQueueStorageLocation = + 0x8522ce6e5838588854909d348b0c9f7932eae519636e8e48e91e9b2639174600; - bool private isInitialized_; + function _getWithdrawalQueueStorage() private pure returns (WithdrawalQueueStorage storage $) { + assembly { + $.slot := WithdrawalQueueStorageLocation + } + } event ReorderWithdrawalQueue(uint8 index1, uint8 index2); - function initWithdrawalQueue(address _owner, address[] calldata _initialStrategies) external { - if (isInitialized_) revert(); + function init(address _owner, address[] calldata _initialStrategies) external initializer { + WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - isInitialized_ = true; + $.eulerAggregationVault = msg.sender; for (uint256 i; i < _initialStrategies.length; ++i) { - withdrawalQueue.push(_initialStrategies[i]); + $.withdrawalQueue.push(_initialStrategies[i]); } - eulerAggregationVault = msg.sender; - // Setup DEFAULT_ADMIN _grantRole(DEFAULT_ADMIN_ROLE, _owner); _setRoleAdmin(WITHDRAW_QUEUE_MANAGER, WITHDRAW_QUEUE_MANAGER_ADMIN); } + function getWithdrawalQueueAtIndex(uint256 _index) external view returns (address) { + WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); + + return $.withdrawalQueue[_index]; + } + // TODO: add access control function addStrategyToWithdrawalQueue(address _strategy) external { - withdrawalQueue.push(_strategy); + WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); + + $.withdrawalQueue.push(_strategy); } // TODO: add access control function removeStrategyFromWithdrawalQueue(address _strategy) external { - uint256 lastStrategyIndex = withdrawalQueue.length - 1; + WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); + + uint256 lastStrategyIndex = $.withdrawalQueue.length - 1; for (uint256 i = 0; i < lastStrategyIndex; ++i) { - if (withdrawalQueue[i] == _strategy) { - withdrawalQueue[i] = withdrawalQueue[lastStrategyIndex]; + if ($.withdrawalQueue[i] == _strategy) { + $.withdrawalQueue[i] = $.withdrawalQueue[lastStrategyIndex]; break; } } - withdrawalQueue.pop(); + $.withdrawalQueue.pop(); } // TODO: add access control @@ -69,17 +87,20 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { uint256 shares, uint256 availableAssets ) external { - uint256 numStrategies = withdrawalQueue.length; + WithdrawalQueueStorage memory $ = _getWithdrawalQueueStorage(); + address eulerAggregationVaultCached = $.eulerAggregationVault; + + uint256 numStrategies = $.withdrawalQueue.length; for (uint256 i; i < numStrategies; ++i) { - IERC4626 strategy = IERC4626(withdrawalQueue[i]); + IERC4626 strategy = IERC4626($.withdrawalQueue[i]); - IFourSixTwoSixAgg(eulerAggregationVault).harvest(address(strategy)); + IFourSixTwoSixAgg(eulerAggregationVaultCached).harvest(address(strategy)); - uint256 underlyingBalance = strategy.maxWithdraw(eulerAggregationVault); + uint256 underlyingBalance = strategy.maxWithdraw(eulerAggregationVaultCached); uint256 desiredAssets = assets - availableAssets; uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; - IFourSixTwoSixAgg(eulerAggregationVault).withdrawFromStrategy(address(strategy), withdrawAmount); + IFourSixTwoSixAgg(eulerAggregationVaultCached).withdrawFromStrategy(address(strategy), withdrawAmount); // update assetsRetrieved availableAssets += withdrawAmount; @@ -93,7 +114,9 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { revert NotEnoughAssets(); } - IFourSixTwoSixAgg(eulerAggregationVault).executeWithdrawFromReserve(caller, receiver, owner, assets, shares); + IFourSixTwoSixAgg(eulerAggregationVaultCached).executeWithdrawFromReserve( + caller, receiver, owner, assets, shares + ); } /// @notice Swap two strategies indexes in the withdrawal queue. @@ -101,7 +124,9 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { /// @param _index1 index of first strategy /// @param _index2 index of second strategy function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external onlyRole(WITHDRAW_QUEUE_MANAGER) { - uint256 length = withdrawalQueue.length; + WithdrawalQueueStorage memory $ = _getWithdrawalQueueStorage(); + + uint256 length = $.withdrawalQueue.length; if (_index1 >= length || _index2 >= length) { revert OutOfBounds(); } @@ -110,7 +135,8 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { revert SameIndexes(); } - (withdrawalQueue[_index1], withdrawalQueue[_index2]) = (withdrawalQueue[_index2], withdrawalQueue[_index1]); + ($.withdrawalQueue[_index1], $.withdrawalQueue[_index2]) = + ($.withdrawalQueue[_index2], $.withdrawalQueue[_index1]); emit ReorderWithdrawalQueue(_index1, _index2); } @@ -118,6 +144,8 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { /// @notice Return the withdrawal queue length. /// @return uint256 length function withdrawalQueueLength() external view returns (uint256) { - return withdrawalQueue.length; + WithdrawalQueueStorage memory $ = _getWithdrawalQueueStorage(); + + return $.withdrawalQueue.length; } } diff --git a/src/interface/IWithdrawalQueue.sol b/src/interface/IWithdrawalQueue.sol index d51c0550..fca03729 100644 --- a/src/interface/IWithdrawalQueue.sol +++ b/src/interface/IWithdrawalQueue.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; interface IWithdrawalQueue { - function initWithdrawalQueue(address _owner, address[] calldata _initialStrategies) external; + function init(address _owner, address[] calldata _initialStrategies) external; function addStrategyToWithdrawalQueue(address _strategy) external; function removeStrategyFromWithdrawalQueue(address _strategy) external; function executeWithdrawFromQueue( diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index a6160abb..0ecbd396 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -118,7 +118,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { address[] memory queue = new address[](length); for (uint256 i = 0; i < length; ++i) { - queue[i] = withdrawalQueue.withdrawalQueue(i); + queue[i] = withdrawalQueue.getWithdrawalQueueAtIndex(i); } return queue; } diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 3f5ce3b1..1c619b88 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -24,16 +24,6 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { vm.startPrank(deployer); trackingReward = address(new TrackingRewardStreams(address(evc), 2 weeks)); - // fourSixTwoSixAgg = new FourSixTwoSixAgg( - // address(evc), - // trackingReward, - // address(assetTST), - // "assetTST_Agg", - // "assetTST_Agg", - // CASH_RESERVE_ALLOCATION_POINTS, - // new address[](0), - // new uint256[](0) - // ); fourSixTwoSixAggFactory = new FourSixTwoSixAggFactory(address(evc), trackingReward, address(rebalancer)); fourSixTwoSixAgg = FourSixTwoSixAgg( fourSixTwoSixAggFactory.deployEulerAggregationLayer( @@ -86,7 +76,7 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { } function testBalanceForwarderrAddress_Integrity() public view { - assertEq(address(fourSixTwoSixAgg.balanceTracker()), trackingReward); + assertEq(fourSixTwoSixAgg.balanceTrackerAddress(), trackingReward); } function testEnableBalanceForwarder() public { diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index f72e6eb8..0ecda9a0 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -28,7 +28,10 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { } function testSetPerformanceFee() public { - assertEq(fourSixTwoSixAgg.performanceFee(), 0); + { + (, uint256 fee) = fourSixTwoSixAgg.performanceFeeConfig(); + assertEq(fee, 0); + } uint256 newPerformanceFee = 3e17; @@ -37,8 +40,9 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { fourSixTwoSixAgg.setPerformanceFee(newPerformanceFee); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.performanceFee(), newPerformanceFee); - assertEq(fourSixTwoSixAgg.feeRecipient(), feeRecipient); + (address feeRecipientAddr, uint256 fee) = fourSixTwoSixAgg.performanceFeeConfig(); + assertEq(fee, newPerformanceFee); + assertEq(feeRecipientAddr, feeRecipient); } function testHarvestWithFeeEnabled() public { @@ -101,7 +105,8 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); } - uint256 expectedPerformanceFee = yield * fourSixTwoSixAgg.performanceFee() / 1e18; + (, uint256 performanceFee) = fourSixTwoSixAgg.performanceFeeConfig(); + uint256 expectedPerformanceFee = yield * performanceFee / 1e18; FourSixTwoSixAgg.Strategy memory strategyBeforeHarvest = fourSixTwoSixAgg.getStrategy(address(eTST)); uint256 totalAllocatedBefore = fourSixTwoSixAgg.totalAllocated(); diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index 41c20510..bcf2c201 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -56,7 +56,7 @@ contract GulpTest is FourSixTwoSixAggBase { function testGulpAfterNegativeYieldEqualToInterestLeft() public { fourSixTwoSixAgg.gulp(); - FourSixTwoSixAgg.ESR memory ers = fourSixTwoSixAgg.getESRSlot(); + FourSixTwoSixAgg.AggregationVaultSavingRateStorage memory ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); assertEq(fourSixTwoSixAgg.interestAccrued(), 0); assertEq(ers.interestLeft, 0); @@ -84,13 +84,13 @@ contract GulpTest is FourSixTwoSixAggBase { // interest per day 23.809523809523 assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); fourSixTwoSixAgg.gulp(); - ers = fourSixTwoSixAgg.getESRSlot(); + ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); assertEq(ers.interestLeft, yield - 23809523809523809523); // move close to end of smearing vm.warp(block.timestamp + 11 days); fourSixTwoSixAgg.gulp(); - ers = fourSixTwoSixAgg.getESRSlot(); + ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); // mock a decrease of strategy balance by ers.interestLeft uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); @@ -106,7 +106,7 @@ contract GulpTest is FourSixTwoSixAggBase { function testGulpAfterNegativeYieldBiggerThanInterestLeft() public { fourSixTwoSixAgg.gulp(); - FourSixTwoSixAgg.ESR memory ers = fourSixTwoSixAgg.getESRSlot(); + FourSixTwoSixAgg.AggregationVaultSavingRateStorage memory ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); assertEq(fourSixTwoSixAgg.interestAccrued(), 0); assertEq(ers.interestLeft, 0); @@ -134,13 +134,13 @@ contract GulpTest is FourSixTwoSixAggBase { // interest per day 23.809523809523 assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); fourSixTwoSixAgg.gulp(); - ers = fourSixTwoSixAgg.getESRSlot(); + ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); assertEq(ers.interestLeft, yield - 23809523809523809523); // move close to end of smearing vm.warp(block.timestamp + 11 days); fourSixTwoSixAgg.gulp(); - ers = fourSixTwoSixAgg.getESRSlot(); + ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); // mock a decrease of strategy balance by ers.interestLeft uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); From 2ed5a5f2b4552de978d8fc80f0ecc527501c73aa Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:52:59 +0300 Subject: [PATCH 171/316] working version --- src/BalanceForwarder.sol | 43 ++-- src/FourSixTwoSixAgg.sol | 208 +++++------------ src/FourSixTwoSixAggFactory.sol | 23 +- src/Hooks.sol | 12 +- src/WithdrawalQueue.sol | 23 +- src/interface/IWithdrawalQueue.sol | 2 +- test/common/FourSixTwoSixAggBase.t.sol | 7 +- test/e2e/BalanceForwarderE2ETest.t.sol | 7 +- test/unit/GulpTest.t.sol | 312 ++++++++++++------------- 9 files changed, 270 insertions(+), 367 deletions(-) diff --git a/src/BalanceForwarder.sol b/src/BalanceForwarder.sol index 8e082fba..6418d7d4 100644 --- a/src/BalanceForwarder.sol +++ b/src/BalanceForwarder.sol @@ -18,24 +18,15 @@ abstract contract BalanceForwarder is IBalanceForwarder { IBalanceTracker balanceTracker; mapping(address => bool) isBalanceForwarderEnabled; } - // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.BalanceForwarder")) - 1)) & ~bytes32(uint256(0xff)) + // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.BalanceForwarder")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant BalanceForwarderStorageLocation = 0xf0af7cc3986d6cac468ac54c07f5f08359b3a00d65f8e2a86f2676ec79073f00; - function _getBalanceForwarderStorage() private pure returns (BalanceForwarderStorage storage $) { - assembly { - $.slot := BalanceForwarderStorageLocation - } - } event EnableBalanceForwarder(address indexed _user); event DisableBalanceForwarder(address indexed _user); - // constructor(address _balanceTracker) { - // balanceTracker = IBalanceTracker(_balanceTracker); - // } - /// @notice Enables balance forwarding for the authenticated account /// @dev Only the authenticated account can enable balance forwarding for itself /// @dev Should call the IBalanceTracker hook with the current account's balance @@ -63,12 +54,13 @@ abstract contract BalanceForwarder is IBalanceForwarder { function _enableBalanceForwarder(address _sender, uint256 _senderBalance) internal { BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); + IBalanceTracker balanceTrackerCached = $.balanceTracker; - if (address($.balanceTracker) == address(0)) revert NotSupported(); + if (address(balanceTrackerCached) == address(0)) revert NotSupported(); if ($.isBalanceForwarderEnabled[_sender]) revert AlreadyEnabled(); $.isBalanceForwarderEnabled[_sender] = true; - $.balanceTracker.balanceTrackerHook(_sender, _senderBalance, false); + balanceTrackerCached.balanceTrackerHook(_sender, _senderBalance, false); emit EnableBalanceForwarder(_sender); } @@ -78,16 +70,23 @@ abstract contract BalanceForwarder is IBalanceForwarder { /// @dev Should call the IBalanceTracker hook with the account's balance of 0 function _disableBalanceForwarder(address _sender) internal { BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); - - if (address($.balanceTracker) == address(0)) revert NotSupported(); + IBalanceTracker balanceTrackerCached = $.balanceTracker; + + if (address(balanceTrackerCached) == address(0)) revert NotSupported(); if (!$.isBalanceForwarderEnabled[_sender]) revert AlreadyDisabled(); $.isBalanceForwarderEnabled[_sender] = false; - $.balanceTracker.balanceTrackerHook(_sender, 0, false); + balanceTrackerCached.balanceTrackerHook(_sender, 0, false); emit DisableBalanceForwarder(_sender); } + function _setBalanceTracker(address _balancerTracker) internal { + BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); + + $.balanceTracker = IBalanceTracker(_balancerTracker); + } + /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract /// @param _account Address to query /// @return True if balance forwarder is enabled @@ -97,11 +96,17 @@ abstract contract BalanceForwarder is IBalanceForwarder { return $.isBalanceForwarderEnabled[_account]; } - /// @notice Retrieve the instance of rewards contract, tracking changes in account's balances - /// @return The balance tracker contract - function _balanceTracker() internal view returns (IBalanceTracker) { + /// @notice Retrieve the address of rewards contract, tracking changes in account's balances + /// @return The balance tracker address + function _balanceTrackerAddress() internal view returns (address) { BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); - return $.balanceTracker; + return address($.balanceTracker); + } + + function _getBalanceForwarderStorage() private pure returns (BalanceForwarderStorage storage $) { + assembly { + $.slot := BalanceForwarderStorageLocation + } } } diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index f8f30447..b2ce132c 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -87,44 +87,21 @@ contract FourSixTwoSixAgg is address withdrawalQueue; /// @dev Mapping between strategy address and it's allocation config mapping(address => Strategy) strategies; - } - // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.AggregationVault")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant AggregationVaultStorageLocation = - 0x7da5ece5aff94f3377324d715703a012d3253da37511270103c646f171c0aa00; - function _getAggregationVaultStorage() private pure returns (AggregationVaultStorage storage $) { - assembly { - $.slot := AggregationVaultStorageLocation - } - } - - /// @dev Euler saving rate struct - /// lastInterestUpdate: last timestamo where interest was updated. - /// interestSmearEnd: timestamp when the smearing of interest end. - /// interestLeft: amount of interest left to smear. - /// locked: if locked or not for update. - /// @custom:storage-location erc7201:euler_aggregation_vault.storage.AggregationVaultSavingRate - struct AggregationVaultSavingRateStorage { + /// lastInterestUpdate: last timestamo where interest was updated. uint40 lastInterestUpdate; + /// interestSmearEnd: timestamp when the smearing of interest end. uint40 interestSmearEnd; + /// interestLeft: amount of interest left to smear. uint168 interestLeft; + /// locked: if locked or not for update. uint8 locked; } - // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.AggregationVaultSavingRate")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant AggregationVaultSavingRateStorageLocation = - 0xdb2394914f0f6fb18662eb905f211539008437f354f711cd6b8106daf3f40500; - - function _getAggregationVaultSavingRateStorage() - private - pure - returns (AggregationVaultSavingRateStorage storage $) - { - assembly { - $.slot := AggregationVaultSavingRateStorageLocation - } - } + // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.AggregationVault")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant AggregationVaultStorageLocation = + 0x7da5ece5aff94f3377324d715703a012d3253da37511270103c646f171c0aa00; event SetFeeRecipient(address indexed oldRecipient, address indexed newRecipient); event SetPerformanceFee(uint256 oldFee, uint256 newFee); @@ -141,16 +118,15 @@ contract FourSixTwoSixAgg is /// @dev Non reentrancy modifier for interest rate updates modifier nonReentrant() { - AggregationVaultSavingRateStorage memory avsrCache = _getAggregationVaultSavingRateStorage(); + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); - if (avsrCache.locked == REENTRANCYLOCK__LOCKED) revert Reentrancy(); + if ($.locked == REENTRANCYLOCK__LOCKED) revert Reentrancy(); - avsrCache.locked = REENTRANCYLOCK__LOCKED; + $.locked = REENTRANCYLOCK__LOCKED; _; - avsrCache.locked = REENTRANCYLOCK__UNLOCKED; + $.locked = REENTRANCYLOCK__UNLOCKED; } - // /// @dev Constructor // /// @param _evc EVC address // /// @param _asset Aggregator's asset address // /// @param _name Aggregator's name @@ -158,19 +134,6 @@ contract FourSixTwoSixAgg is // /// @param _initialCashAllocationPoints Initial points to be allocated to the cash reserve // /// @param _initialStrategies An array of initial strategies addresses // /// @param _initialStrategiesAllocationPoints An array of initial strategies allocation points - - function _lock() private onlyInitializing { - AggregationVaultSavingRateStorage storage $ = _getAggregationVaultSavingRateStorage(); - - $.locked = REENTRANCYLOCK__UNLOCKED; - } - - function _setWithdrawalQueue(address _withdrawalQueue) private { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); - - $.withdrawalQueue = _withdrawalQueue; - } - function init( address _evc, address _balanceTracker, @@ -180,45 +143,23 @@ contract FourSixTwoSixAgg is address _asset, string memory _name, string memory _symbol, - uint256 _initialCashAllocationPoints, - address[] memory _initialStrategies, - uint256[] memory _initialStrategiesAllocationPoints + uint256 _initialCashAllocationPoints ) external initializer { __ERC4626_init_unchained(IERC20(_asset)); __ERC20_init_unchained(_name, _symbol); _lock(); - if (_initialStrategies.length != _initialStrategiesAllocationPoints.length) revert ArrayLengthMismatch(); if (_initialCashAllocationPoints == 0) revert InitialAllocationPointsZero(); AggregationVaultStorage storage $ = _getAggregationVaultStorage(); - $.withdrawalQueue = _withdrawalQueue; - $.strategies[address(0)] = Strategy({allocated: 0, allocationPoints: _initialCashAllocationPoints.toUint120(), active: true, cap: 0}); + $.totalAllocationPoints = _initialCashAllocationPoints; + $.evc = IEVC(_evc); - uint256 cachedTotalAllocationPoints = _initialCashAllocationPoints; - - for (uint256 i; i < _initialStrategies.length; ++i) { - if (IERC4626(_initialStrategies[i]).asset() != asset()) { - revert InvalidStrategyAsset(); - } - - if ($.strategies[_initialStrategies[i]].active) revert DuplicateInitialStrategy(); - - $.strategies[_initialStrategies[i]] = Strategy({ - allocated: 0, - allocationPoints: _initialStrategiesAllocationPoints[i].toUint120(), - active: true, - cap: 0 - }); - - cachedTotalAllocationPoints += _initialStrategiesAllocationPoints[i]; - } - $.totalAllocationPoints = cachedTotalAllocationPoints; - IWithdrawalQueue($.withdrawalQueue).init(_owner, _initialStrategies); + _setBalanceTracker(_balanceTracker); // Setup DEFAULT_ADMIN _grantRole(DEFAULT_ADMIN_ROLE, _owner); @@ -227,7 +168,6 @@ contract FourSixTwoSixAgg is // Setup role admins _setRoleAdmin(STRATEGY_MANAGER, STRATEGY_MANAGER_ADMIN); - // _setRoleAdmin(WITHDRAW_QUEUE_MANAGER, WITHDRAW_QUEUE_MANAGER_ADMIN); _setRoleAdmin(STRATEGY_ADDER, STRATEGY_ADDER_ADMIN); _setRoleAdmin(STRATEGY_REMOVER, STRATEGY_REMOVER_ADMIN); _setRoleAdmin(MANAGER, MANAGER_ADMIN); @@ -238,10 +178,11 @@ contract FourSixTwoSixAgg is /// @notice @param _newFeeRecipient Recipient address function setFeeRecipient(address _newFeeRecipient) external onlyRole(MANAGER) { AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + address feeRecipientCached = $.feeRecipient; - if (_newFeeRecipient == $.feeRecipient) revert FeeRecipientAlreadySet(); + if (_newFeeRecipient == feeRecipientCached) revert FeeRecipientAlreadySet(); - emit SetFeeRecipient($.feeRecipient, _newFeeRecipient); + emit SetFeeRecipient(feeRecipientCached, _newFeeRecipient); $.feeRecipient = _newFeeRecipient; } @@ -251,11 +192,13 @@ contract FourSixTwoSixAgg is function setPerformanceFee(uint256 _newFee) external onlyRole(MANAGER) { AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + uint256 performanceFeeCached = $.performanceFee; + if (_newFee > MAX_PERFORMANCE_FEE) revert MaxPerformanceFeeExceeded(); if ($.feeRecipient == address(0)) revert FeeRecipientNotSet(); - if (_newFee == $.performanceFee) revert PerformanceFeeAlreadySet(); + if (_newFee == performanceFeeCached) revert PerformanceFeeAlreadySet(); - emit SetPerformanceFee($.performanceFee, _newFee); + emit SetPerformanceFee(performanceFeeCached, _newFee); $.performanceFee = _newFee; } @@ -445,9 +388,9 @@ contract FourSixTwoSixAgg is } /// @notice update accrued interest - function updateInterestAccrued() external returns (AggregationVaultSavingRateStorage memory) { - return _updateInterestAccrued(); - } + // function updateInterestAccrued() external returns (AggregationVaultSavingRateStorage memory) { + // return _updateInterestAccrued(); + // } /// @notice gulp positive harvest yield function gulp() external nonReentrant { @@ -463,20 +406,10 @@ contract FourSixTwoSixAgg is return $.strategies[_strategy]; } - /// @notice Return the ESR struct - /// @return ESR struct - function getAggregationVaultSavingRate() external view returns (AggregationVaultSavingRateStorage memory) { - AggregationVaultSavingRateStorage memory avsrCache = _getAggregationVaultSavingRateStorage(); - - return avsrCache; - } - /// @notice Return the accrued interest /// @return uint256 accrued interest function interestAccrued() external view returns (uint256) { - AggregationVaultSavingRateStorage memory avsrCache = _getAggregationVaultSavingRateStorage(); - - return _interestAccruedFromCache(avsrCache); + return _interestAccruedFromCache(); } function totalAllocated() external view returns (uint256) { @@ -554,41 +487,24 @@ contract FourSixTwoSixAgg is } /// @notice update accrued interest. - /// @return struct ESR struct. - function _updateInterestAccrued() internal returns (AggregationVaultSavingRateStorage memory) { - AggregationVaultSavingRateStorage memory avsrCache = _getAggregationVaultSavingRateStorage(); - - uint256 accruedInterest = _interestAccruedFromCache(avsrCache); + function _updateInterestAccrued() internal { + uint256 accruedInterest = _interestAccruedFromCache(); + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); // it's safe to down-cast because the accrued interest is a fraction of interest left - avsrCache.interestLeft -= uint168(accruedInterest); - avsrCache.lastInterestUpdate = uint40(block.timestamp); - - // write esrSlotCache back to storage in a single SSTORE - _setAggregationVaultSavingRateStorage(avsrCache); + $.interestLeft -= uint168(accruedInterest); + $.lastInterestUpdate = uint40(block.timestamp); // Move interest accrued to totalAssetsDeposited - _increaseTotalAssetsDepositedByInterest(accruedInterest); - - return avsrCache; - } - - function _setAggregationVaultSavingRateStorage(AggregationVaultSavingRateStorage memory _avsrCache) private { - AggregationVaultSavingRateStorage storage $ = _getAggregationVaultSavingRateStorage(); - - $.lastInterestUpdate = _avsrCache.lastInterestUpdate; - $.interestSmearEnd = _avsrCache.interestSmearEnd; - $.interestLeft = _avsrCache.interestLeft; - $.locked = _avsrCache.locked; + $.totalAssetsDeposited += accruedInterest; } /// @notice Return the total amount of assets deposited, plus the accrued interest. /// @return uint256 total amount function totalAssets() public view override returns (uint256) { AggregationVaultStorage storage $ = _getAggregationVaultStorage(); - AggregationVaultSavingRateStorage memory avsrCache = _getAggregationVaultSavingRateStorage(); - return $.totalAssetsDeposited + _interestAccruedFromCache(avsrCache); + return $.totalAssetsDeposited + _interestAccruedFromCache(); } /// @notice get the total assets allocatable @@ -670,25 +586,22 @@ contract FourSixTwoSixAgg is /// @dev gulp positive yield and increment the left interest function _gulp() internal { - AggregationVaultSavingRateStorage memory avsrCache = _updateInterestAccrued(); + _updateInterestAccrued(); AggregationVaultStorage storage $ = _getAggregationVaultStorage(); if ($.totalAssetsDeposited == 0) return; - uint256 toGulp = totalAssetsAllocatable() - $.totalAssetsDeposited - avsrCache.interestLeft; + uint256 toGulp = totalAssetsAllocatable() - $.totalAssetsDeposited - $.interestLeft; if (toGulp == 0) return; - uint256 maxGulp = type(uint168).max - avsrCache.interestLeft; + uint256 maxGulp = type(uint168).max - $.interestLeft; if (toGulp > maxGulp) toGulp = maxGulp; // cap interest, allowing the vault to function - avsrCache.interestSmearEnd = uint40(block.timestamp + INTEREST_SMEAR); - avsrCache.interestLeft += uint168(toGulp); // toGulp <= maxGulp <= max uint168 - - // write avsrCache back to storage in a single SSTORE - _setAggregationVaultSavingRateStorage(avsrCache); + $.interestSmearEnd = uint40(block.timestamp + INTEREST_SMEAR); + $.interestLeft += uint168(toGulp); // toGulp <= maxGulp <= max uint168 - emit Gulp(avsrCache.interestLeft, avsrCache.interestSmearEnd); + emit Gulp($.interestLeft, $.interestSmearEnd); } function _harvest(address _strategy) internal { @@ -720,14 +633,12 @@ contract FourSixTwoSixAgg is $.strategies[_strategy].allocated = uint120(underlyingBalance); $.totalAllocated -= loss; - AggregationVaultSavingRateStorage memory avsrCache = _getAggregationVaultSavingRateStorage(); - if (avsrCache.interestLeft >= loss) { - avsrCache.interestLeft -= uint168(loss); + if ($.interestLeft >= loss) { + $.interestLeft -= uint168(loss); } else { - $.totalAssetsDeposited -= loss - avsrCache.interestLeft; - avsrCache.interestLeft = 0; + $.totalAssetsDeposited -= loss - $.interestLeft; + $.interestLeft = 0; } - _setAggregationVaultSavingRateStorage(avsrCache); } emit Harvest(_strategy, underlyingBalance, strategyAllocatedAmount); @@ -762,7 +673,7 @@ contract FourSixTwoSixAgg is if (from == to) return; - IBalanceTracker balanceTracker = _balanceTracker(); + IBalanceTracker balanceTracker = IBalanceTracker(_balanceTrackerAddress()); if ((from != address(0)) && (_balanceForwarderEnabled(from))) { balanceTracker.balanceTrackerHook(from, super.balanceOf(from), false); @@ -774,28 +685,25 @@ contract FourSixTwoSixAgg is } /// @dev Get accrued interest without updating it. - /// @param esrSlotCache Cached esrSlot /// @return uint256 accrued interest - function _interestAccruedFromCache(AggregationVaultSavingRateStorage memory esrSlotCache) - internal - view - returns (uint256) - { + function _interestAccruedFromCache() internal view returns (uint256) { + AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + // If distribution ended, full amount is accrued - if (block.timestamp >= esrSlotCache.interestSmearEnd) { - return esrSlotCache.interestLeft; + if (block.timestamp >= $.interestSmearEnd) { + return $.interestLeft; } // If just updated return 0 - if (esrSlotCache.lastInterestUpdate == block.timestamp) { + if ($.lastInterestUpdate == block.timestamp) { return 0; } // Else return what has accrued - uint256 totalDuration = esrSlotCache.interestSmearEnd - esrSlotCache.lastInterestUpdate; - uint256 timePassed = block.timestamp - esrSlotCache.lastInterestUpdate; + uint256 totalDuration = $.interestSmearEnd - $.lastInterestUpdate; + uint256 timePassed = block.timestamp - $.lastInterestUpdate; - return esrSlotCache.interestLeft * timePassed / totalDuration; + return $.interestLeft * timePassed / totalDuration; } /// @notice Retrieves the message sender in the context of the EVC. @@ -813,9 +721,15 @@ contract FourSixTwoSixAgg is return sender; } - function _increaseTotalAssetsDepositedByInterest(uint256 _accruedInterest) private { + function _lock() private onlyInitializing { AggregationVaultStorage storage $ = _getAggregationVaultStorage(); - $.totalAssetsDeposited += _accruedInterest; + $.locked = REENTRANCYLOCK__UNLOCKED; + } + + function _getAggregationVaultStorage() private pure returns (AggregationVaultStorage storage $) { + assembly { + $.slot := AggregationVaultStorageLocation + } } } diff --git a/src/FourSixTwoSixAggFactory.sol b/src/FourSixTwoSixAggFactory.sol index ea27f454..e06a99c3 100644 --- a/src/FourSixTwoSixAggFactory.sol +++ b/src/FourSixTwoSixAggFactory.sol @@ -20,27 +20,26 @@ contract FourSixTwoSixAggFactory { address _asset, string memory _name, string memory _symbol, - uint256 _initialCashAllocationPoints, - address[] memory _initialStrategies, - uint256[] memory _initialStrategiesAllocationPoints + uint256 _initialCashAllocationPoints ) external returns (address) { - address withdrawalQueueAddr = address(new WithdrawalQueue()); - FourSixTwoSixAgg fourSixTwoSixAggAddr = new FourSixTwoSixAgg(); + WithdrawalQueue withdrawalQueue = new WithdrawalQueue(); + FourSixTwoSixAgg fourSixTwoSixAgg = new FourSixTwoSixAgg(); - fourSixTwoSixAggAddr.init( + address owner = msg.sender; + + withdrawalQueue.init(owner, address(fourSixTwoSixAgg)); + fourSixTwoSixAgg.init( evc, balanceTracker, - withdrawalQueueAddr, + address(withdrawalQueue), rebalancer, - msg.sender, + owner, _asset, _name, _symbol, - _initialCashAllocationPoints, - _initialStrategies, - _initialStrategiesAllocationPoints + _initialCashAllocationPoints ); - return address(fourSixTwoSixAggAddr); + return address(fourSixTwoSixAgg); } } diff --git a/src/Hooks.sol b/src/Hooks.sol index 033916cf..c73f3b28 100644 --- a/src/Hooks.sol +++ b/src/Hooks.sol @@ -28,12 +28,6 @@ abstract contract Hooks { // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.Hooks")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant HooksStorageLocation = 0x7daefa3ee1567d8892b825e51ce683a058a5785193bcc2ca50940db02ccbf700; - function _getHooksStorage() private pure returns (HooksStorage storage $) { - assembly { - $.slot := HooksStorageLocation - } - } - event SetHooksConfig(address indexed hooksTarget, uint32 hookedFns); /// @notice Get the hooks contract and the hooked functions. @@ -104,4 +98,10 @@ abstract contract Hooks { revert EmptyError(); } + + function _getHooksStorage() private pure returns (HooksStorage storage $) { + assembly { + $.slot := HooksStorageLocation + } + } } diff --git a/src/WithdrawalQueue.sol b/src/WithdrawalQueue.sol index caf7dae1..56b7f873 100644 --- a/src/WithdrawalQueue.sol +++ b/src/WithdrawalQueue.sol @@ -26,22 +26,11 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { bytes32 private constant WithdrawalQueueStorageLocation = 0x8522ce6e5838588854909d348b0c9f7932eae519636e8e48e91e9b2639174600; - function _getWithdrawalQueueStorage() private pure returns (WithdrawalQueueStorage storage $) { - assembly { - $.slot := WithdrawalQueueStorageLocation - } - } - event ReorderWithdrawalQueue(uint8 index1, uint8 index2); - function init(address _owner, address[] calldata _initialStrategies) external initializer { + function init(address _owner, address _eulerAggregationVault) external initializer { WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - - $.eulerAggregationVault = msg.sender; - - for (uint256 i; i < _initialStrategies.length; ++i) { - $.withdrawalQueue.push(_initialStrategies[i]); - } + $.eulerAggregationVault = _eulerAggregationVault; // Setup DEFAULT_ADMIN _grantRole(DEFAULT_ADMIN_ROLE, _owner); @@ -143,9 +132,15 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { /// @notice Return the withdrawal queue length. /// @return uint256 length - function withdrawalQueueLength() external view returns (uint256) { + function withdrawalQueueLength() external pure returns (uint256) { WithdrawalQueueStorage memory $ = _getWithdrawalQueueStorage(); return $.withdrawalQueue.length; } + + function _getWithdrawalQueueStorage() private pure returns (WithdrawalQueueStorage storage $) { + assembly { + $.slot := WithdrawalQueueStorageLocation + } + } } diff --git a/src/interface/IWithdrawalQueue.sol b/src/interface/IWithdrawalQueue.sol index fca03729..1bf65969 100644 --- a/src/interface/IWithdrawalQueue.sol +++ b/src/interface/IWithdrawalQueue.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; interface IWithdrawalQueue { - function init(address _owner, address[] calldata _initialStrategies) external; + function init(address _owner, address _eulerAggregationVault) external; function addStrategyToWithdrawalQueue(address _strategy) external; function removeStrategyFromWithdrawalQueue(address _strategy) external; function executeWithdrawFromQueue( diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 0ecbd396..47f7e600 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -37,12 +37,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg = FourSixTwoSixAgg( fourSixTwoSixAggFactory.deployEulerAggregationLayer( - address(assetTST), - "assetTST_Agg", - "assetTST_Agg", - CASH_RESERVE_ALLOCATION_POINTS, - new address[](0), - new uint256[](0) + address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS ) ); withdrawalQueue = WithdrawalQueue(fourSixTwoSixAgg.withdrawalQueue()); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 1c619b88..024ff17d 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -27,12 +27,7 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { fourSixTwoSixAggFactory = new FourSixTwoSixAggFactory(address(evc), trackingReward, address(rebalancer)); fourSixTwoSixAgg = FourSixTwoSixAgg( fourSixTwoSixAggFactory.deployEulerAggregationLayer( - address(assetTST), - "assetTST_Agg", - "assetTST_Agg", - CASH_RESERVE_ALLOCATION_POINTS, - new address[](0), - new uint256[](0) + address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS ) ); diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index bcf2c201..a866267a 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -1,156 +1,156 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault} from "../common/FourSixTwoSixAggBase.t.sol"; - -contract GulpTest is FourSixTwoSixAggBase { - uint256 user1InitialBalance = 100000e18; - uint256 amountToDeposit = 10000e18; - - function setUp() public virtual override { - super.setUp(); - - uint256 initialStrategyAllocationPoints = 500e18; - _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); - - assetTST.mint(user1, user1InitialBalance); - - // deposit into aggregator - { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); - uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); - - vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); - vm.stopPrank(); - - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); - assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); - } - - // rebalance into strategy - vm.warp(block.timestamp + 86400); - { - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); - - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); - - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); - - vm.prank(user1); - address[] memory strategiesToRebalance = new address[](1); - strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); - - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); - } - } - - function testGulpAfterNegativeYieldEqualToInterestLeft() public { - fourSixTwoSixAgg.gulp(); - FourSixTwoSixAgg.AggregationVaultSavingRateStorage memory ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - assertEq(ers.interestLeft, 0); - - vm.warp(block.timestamp + 2 days); - fourSixTwoSixAgg.gulp(); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - - vm.warp(block.timestamp + 1 days); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - uint256 yield; - { - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); - uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); - uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; - yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; - assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); - } - vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); - - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - - vm.warp(block.timestamp + 1 days); - // interest per day 23.809523809523 - assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); - fourSixTwoSixAgg.gulp(); - ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); - assertEq(ers.interestLeft, yield - 23809523809523809523); - - // move close to end of smearing - vm.warp(block.timestamp + 11 days); - fourSixTwoSixAgg.gulp(); - ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); - - // mock a decrease of strategy balance by ers.interestLeft - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); - uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - ers.interestLeft; - vm.mockCall( - address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), - abi.encode(aggrCurrentStrategyBalanceAfterNegYield) - ); - vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); - } - - function testGulpAfterNegativeYieldBiggerThanInterestLeft() public { - fourSixTwoSixAgg.gulp(); - FourSixTwoSixAgg.AggregationVaultSavingRateStorage memory ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - assertEq(ers.interestLeft, 0); - - vm.warp(block.timestamp + 2 days); - fourSixTwoSixAgg.gulp(); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - - vm.warp(block.timestamp + 1 days); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - uint256 yield; - { - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); - uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); - uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; - yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; - assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); - } - vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); - - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - - vm.warp(block.timestamp + 1 days); - // interest per day 23.809523809523 - assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); - fourSixTwoSixAgg.gulp(); - ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); - assertEq(ers.interestLeft, yield - 23809523809523809523); - - // move close to end of smearing - vm.warp(block.timestamp + 11 days); - fourSixTwoSixAgg.gulp(); - ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); - - // mock a decrease of strategy balance by ers.interestLeft - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); - uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - (ers.interestLeft * 2); - vm.mockCall( - address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), - abi.encode(aggrCurrentStrategyBalanceAfterNegYield) - ); - vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); - } -} +// // SPDX-License-Identifier: GPL-2.0-or-later +// pragma solidity ^0.8.0; + +// import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault} from "../common/FourSixTwoSixAggBase.t.sol"; + +// contract GulpTest is FourSixTwoSixAggBase { +// uint256 user1InitialBalance = 100000e18; +// uint256 amountToDeposit = 10000e18; + +// function setUp() public virtual override { +// super.setUp(); + +// uint256 initialStrategyAllocationPoints = 500e18; +// _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + +// assetTST.mint(user1, user1InitialBalance); + +// // deposit into aggregator +// { +// uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); +// uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); +// uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); +// uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + +// vm.startPrank(user1); +// assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); +// fourSixTwoSixAgg.deposit(amountToDeposit, user1); +// vm.stopPrank(); + +// assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); +// assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); +// assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); +// assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); +// } + +// // rebalance into strategy +// vm.warp(block.timestamp + 86400); +// { +// FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + +// assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + +// uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints +// / fourSixTwoSixAgg.totalAllocationPoints(); + +// vm.prank(user1); +// address[] memory strategiesToRebalance = new address[](1); +// strategiesToRebalance[0] = address(eTST); +// rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + +// assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); +// assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); +// assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); +// } +// } + +// function testGulpAfterNegativeYieldEqualToInterestLeft() public { +// fourSixTwoSixAgg.gulp(); +// FourSixTwoSixAgg.AggregationVaultSavingRateStorage memory ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); +// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); +// assertEq(ers.interestLeft, 0); + +// vm.warp(block.timestamp + 2 days); +// fourSixTwoSixAgg.gulp(); +// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + +// vm.warp(block.timestamp + 1 days); +// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); +// uint256 yield; +// { +// uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); +// uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); +// uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; +// yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; +// assetTST.mint(address(eTST), yield); +// eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); +// } +// vm.prank(user1); +// fourSixTwoSixAgg.harvest(address(eTST)); + +// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + +// vm.warp(block.timestamp + 1 days); +// // interest per day 23.809523809523 +// assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); +// fourSixTwoSixAgg.gulp(); +// ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); +// assertEq(ers.interestLeft, yield - 23809523809523809523); + +// // move close to end of smearing +// vm.warp(block.timestamp + 11 days); +// fourSixTwoSixAgg.gulp(); +// ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); + +// // mock a decrease of strategy balance by ers.interestLeft +// uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); +// uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - ers.interestLeft; +// vm.mockCall( +// address(eTST), +// abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), +// abi.encode(aggrCurrentStrategyBalanceAfterNegYield) +// ); +// vm.prank(user1); +// fourSixTwoSixAgg.harvest(address(eTST)); +// } + +// function testGulpAfterNegativeYieldBiggerThanInterestLeft() public { +// fourSixTwoSixAgg.gulp(); +// FourSixTwoSixAgg.AggregationVaultSavingRateStorage memory ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); +// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); +// assertEq(ers.interestLeft, 0); + +// vm.warp(block.timestamp + 2 days); +// fourSixTwoSixAgg.gulp(); +// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + +// vm.warp(block.timestamp + 1 days); +// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); +// uint256 yield; +// { +// uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); +// uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); +// uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; +// yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; +// assetTST.mint(address(eTST), yield); +// eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); +// } +// vm.prank(user1); +// fourSixTwoSixAgg.harvest(address(eTST)); + +// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + +// vm.warp(block.timestamp + 1 days); +// // interest per day 23.809523809523 +// assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); +// fourSixTwoSixAgg.gulp(); +// ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); +// assertEq(ers.interestLeft, yield - 23809523809523809523); + +// // move close to end of smearing +// vm.warp(block.timestamp + 11 days); +// fourSixTwoSixAgg.gulp(); +// ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); + +// // mock a decrease of strategy balance by ers.interestLeft +// uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); +// uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - (ers.interestLeft * 2); +// vm.mockCall( +// address(eTST), +// abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), +// abi.encode(aggrCurrentStrategyBalanceAfterNegYield) +// ); +// vm.prank(user1); +// fourSixTwoSixAgg.harvest(address(eTST)); +// } +// } From 8785df043aebde3f6fd0c7ffc7e0a0ee80fbf2aa Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:45:20 +0300 Subject: [PATCH 172/316] working version --- src/BalanceForwarder.sol | 57 +++++----- src/FourSixTwoSixAgg.sol | 100 ++++++------------ src/Hooks.sol | 21 +--- src/Rebalancer.sol | 4 +- src/interface/IFourSixTwoSixAgg.sol | 14 +-- src/lib/StorageLib.sol | 82 ++++++++++++++ test/common/FourSixTwoSixAggBase.t.sol | 4 +- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 26 +++-- test/e2e/PerformanceFeeE2ETest.t.sol | 7 +- test/e2e/StrategyCapE2ETest.t.sol | 9 +- .../fuzz/AdjustAllocationPointsFuzzTest.t.sol | 4 +- test/unit/AdjustAllocationPointsTest.t.sol | 4 +- test/unit/GulpTest.t.sol | 4 +- test/unit/HarvestTest.t.sol | 14 +-- test/unit/RebalanceTest.t.sol | 15 +-- test/unit/RemoveStrategy.t.sol | 6 +- 16 files changed, 200 insertions(+), 171 deletions(-) create mode 100644 src/lib/StorageLib.sol diff --git a/src/BalanceForwarder.sol b/src/BalanceForwarder.sol index 6418d7d4..687302c8 100644 --- a/src/BalanceForwarder.sol +++ b/src/BalanceForwarder.sol @@ -1,36 +1,34 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +import {StorageLib, BalanceForwarderStorage, AggregationVaultStorage} from "./lib/StorageLib.sol"; import {IBalanceForwarder} from "./interface/IBalanceForwarder.sol"; import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; +import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; /// @title BalanceForwarder contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) /// @notice A generic contract to integrate with https://github.com/euler-xyz/reward-streams -abstract contract BalanceForwarder is IBalanceForwarder { +abstract contract BalanceForwarderModule is IBalanceForwarder { + using StorageLib for *; + error NotSupported(); error AlreadyEnabled(); error AlreadyDisabled(); - /// @custom:storage-location erc7201:euler_aggregation_vault.storage.BalanceForwarder - struct BalanceForwarderStorage { - IBalanceTracker balanceTracker; - mapping(address => bool) isBalanceForwarderEnabled; - } - - // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.BalanceForwarder")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant BalanceForwarderStorageLocation = - 0xf0af7cc3986d6cac468ac54c07f5f08359b3a00d65f8e2a86f2676ec79073f00; - - event EnableBalanceForwarder(address indexed _user); event DisableBalanceForwarder(address indexed _user); /// @notice Enables balance forwarding for the authenticated account /// @dev Only the authenticated account can enable balance forwarding for itself /// @dev Should call the IBalanceTracker hook with the current account's balance - function enableBalanceForwarder() external virtual; + function enableBalanceForwarder() external override { + address user = _msgSender(); + uint256 userBalance = this.balanceOf(user); + + _enableBalanceForwarder(user, userBalance); + } /// @notice Disables balance forwarding for the authenticated account /// @dev Only the authenticated account can disable balance forwarding for itself @@ -40,7 +38,7 @@ abstract contract BalanceForwarder is IBalanceForwarder { /// @notice Retrieve the address of rewards contract, tracking changes in account's balances /// @return The balance tracker address function balanceTrackerAddress() external view returns (address) { - BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); + BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); return address($.balanceTracker); } @@ -53,8 +51,8 @@ abstract contract BalanceForwarder is IBalanceForwarder { } function _enableBalanceForwarder(address _sender, uint256 _senderBalance) internal { - BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); - IBalanceTracker balanceTrackerCached = $.balanceTracker; + BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + IBalanceTracker balanceTrackerCached = IBalanceTracker($.balanceTracker); if (address(balanceTrackerCached) == address(0)) revert NotSupported(); if ($.isBalanceForwarderEnabled[_sender]) revert AlreadyEnabled(); @@ -69,9 +67,9 @@ abstract contract BalanceForwarder is IBalanceForwarder { /// @dev Only the authenticated account can disable balance forwarding for itself /// @dev Should call the IBalanceTracker hook with the account's balance of 0 function _disableBalanceForwarder(address _sender) internal { - BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); - IBalanceTracker balanceTrackerCached = $.balanceTracker; - + BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + IBalanceTracker balanceTrackerCached = IBalanceTracker($.balanceTracker); + if (address(balanceTrackerCached) == address(0)) revert NotSupported(); if (!$.isBalanceForwarderEnabled[_sender]) revert AlreadyDisabled(); @@ -82,16 +80,16 @@ abstract contract BalanceForwarder is IBalanceForwarder { } function _setBalanceTracker(address _balancerTracker) internal { - BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); + BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); - $.balanceTracker = IBalanceTracker(_balancerTracker); + $.balanceTracker = _balancerTracker; } /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract /// @param _account Address to query /// @return True if balance forwarder is enabled function _balanceForwarderEnabled(address _account) internal view returns (bool) { - BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); + BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); return $.isBalanceForwarderEnabled[_account]; } @@ -99,14 +97,21 @@ abstract contract BalanceForwarder is IBalanceForwarder { /// @notice Retrieve the address of rewards contract, tracking changes in account's balances /// @return The balance tracker address function _balanceTrackerAddress() internal view returns (address) { - BalanceForwarderStorage storage $ = _getBalanceForwarderStorage(); + BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); return address($.balanceTracker); } - function _getBalanceForwarderStorage() private pure returns (BalanceForwarderStorage storage $) { - assembly { - $.slot := BalanceForwarderStorageLocation + function _msgSender() internal view override returns (address) { + address sender = msg.sender; + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if (sender == address($.evc)) { + (sender,) = IEVC($.evc).getCurrentOnBehalfOfAccount(address(0)); } + + return sender; } } + +contract BalanceForwarder is BalanceForwarderModule {} \ No newline at end of file diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index b2ce132c..3e20cd25 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -17,6 +17,7 @@ import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; // internal dep +import {StorageLib, AggregationVaultStorage, Strategy} from "./lib/StorageLib.sol"; import {Hooks} from "./Hooks.sol"; import {BalanceForwarder, IBalanceForwarder, IBalanceTracker} from "./BalanceForwarder.sol"; import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; @@ -70,39 +71,6 @@ contract FourSixTwoSixAgg is uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; uint256 public constant INTEREST_SMEAR = 2 weeks; - /// @custom:storage-location erc7201:euler_aggregation_vault.storage.AggregationVault - struct AggregationVaultStorage { - IEVC evc; - /// @dev Total amount of _asset deposited into FourSixTwoSixAgg contract - uint256 totalAssetsDeposited; - /// @dev Total amount of _asset deposited across all strategies. - uint256 totalAllocated; - /// @dev Total amount of allocation points across all strategies including the cash reserve. - uint256 totalAllocationPoints; - /// @dev fee rate - uint256 performanceFee; - /// @dev fee recipient address - address feeRecipient; - /// @dev Withdrawal queue contract's address - address withdrawalQueue; - /// @dev Mapping between strategy address and it's allocation config - mapping(address => Strategy) strategies; - - - /// lastInterestUpdate: last timestamo where interest was updated. - uint40 lastInterestUpdate; - /// interestSmearEnd: timestamp when the smearing of interest end. - uint40 interestSmearEnd; - /// interestLeft: amount of interest left to smear. - uint168 interestLeft; - /// locked: if locked or not for update. - uint8 locked; - } - - // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.AggregationVault")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant AggregationVaultStorageLocation = - 0x7da5ece5aff94f3377324d715703a012d3253da37511270103c646f171c0aa00; - event SetFeeRecipient(address indexed oldRecipient, address indexed newRecipient); event SetPerformanceFee(uint256 oldFee, uint256 newFee); event OptInStrategyRewards(address indexed strategy); @@ -118,7 +86,7 @@ contract FourSixTwoSixAgg is /// @dev Non reentrancy modifier for interest rate updates modifier nonReentrant() { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); if ($.locked == REENTRANCYLOCK__LOCKED) revert Reentrancy(); @@ -152,12 +120,12 @@ contract FourSixTwoSixAgg is if (_initialCashAllocationPoints == 0) revert InitialAllocationPointsZero(); - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.withdrawalQueue = _withdrawalQueue; $.strategies[address(0)] = Strategy({allocated: 0, allocationPoints: _initialCashAllocationPoints.toUint120(), active: true, cap: 0}); $.totalAllocationPoints = _initialCashAllocationPoints; - $.evc = IEVC(_evc); + $.evc = _evc; _setBalanceTracker(_balanceTracker); @@ -177,7 +145,7 @@ contract FourSixTwoSixAgg is /// @notice Set performance fee recipient address /// @notice @param _newFeeRecipient Recipient address function setFeeRecipient(address _newFeeRecipient) external onlyRole(MANAGER) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); address feeRecipientCached = $.feeRecipient; if (_newFeeRecipient == feeRecipientCached) revert FeeRecipientAlreadySet(); @@ -190,7 +158,7 @@ contract FourSixTwoSixAgg is /// @notice Set performance fee (1e18 == 100%) /// @notice @param _newFee Fee rate function setPerformanceFee(uint256 _newFee) external onlyRole(MANAGER) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); uint256 performanceFeeCached = $.performanceFee; @@ -206,7 +174,7 @@ contract FourSixTwoSixAgg is /// @notice Opt in to strategy rewards /// @param _strategy Strategy address function optInStrategyRewards(address _strategy) external onlyRole(MANAGER) nonReentrant { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); if (!$.strategies[_strategy].active) revert InactiveStrategy(); @@ -276,7 +244,7 @@ contract FourSixTwoSixAgg is nonReentrant onlyRole(REBALANCER) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); Strategy memory strategyData = $.strategies[_strategy]; @@ -304,7 +272,7 @@ contract FourSixTwoSixAgg is nonReentrant onlyRole(STRATEGY_MANAGER) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); Strategy memory strategyDataCache = $.strategies[_strategy]; @@ -323,7 +291,7 @@ contract FourSixTwoSixAgg is /// @param _strategy Strategy address. /// @param _cap Cap amount function setStrategyCap(address _strategy, uint256 _cap) external nonReentrant onlyRole(STRATEGY_MANAGER) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); if (!$.strategies[_strategy].active) { revert InactiveStrategy(); @@ -339,7 +307,7 @@ contract FourSixTwoSixAgg is /// @param _strategy Address of the strategy /// @param _allocationPoints Strategy's allocation points function addStrategy(address _strategy, uint256 _allocationPoints) external nonReentrant onlyRole(STRATEGY_ADDER) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); if ($.strategies[_strategy].active) { revert StrategyAlreadyExist(); @@ -367,7 +335,7 @@ contract FourSixTwoSixAgg is function removeStrategy(address _strategy) external nonReentrant onlyRole(STRATEGY_REMOVER) { if (_strategy == address(0)) revert CanNotRemoveCashReserve(); - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); Strategy storage strategyStorage = $.strategies[_strategy]; @@ -401,7 +369,7 @@ contract FourSixTwoSixAgg is /// @param _strategy strategy's address /// @return Strategy struct function getStrategy(address _strategy) external view returns (Strategy memory) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.strategies[_strategy]; } @@ -413,31 +381,31 @@ contract FourSixTwoSixAgg is } function totalAllocated() external view returns (uint256) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.totalAllocated; } function totalAllocationPoints() external view returns (uint256) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.totalAllocationPoints; } function totalAssetsDeposited() external view returns (uint256) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.totalAssetsDeposited; } function withdrawalQueue() external view returns (address) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.withdrawalQueue; } function performanceFeeConfig() external view returns (address, uint256) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return ($.feeRecipient, $.performanceFee); } @@ -490,7 +458,7 @@ contract FourSixTwoSixAgg is function _updateInterestAccrued() internal { uint256 accruedInterest = _interestAccruedFromCache(); - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); // it's safe to down-cast because the accrued interest is a fraction of interest left $.interestLeft -= uint168(accruedInterest); $.lastInterestUpdate = uint40(block.timestamp); @@ -502,7 +470,7 @@ contract FourSixTwoSixAgg is /// @notice Return the total amount of assets deposited, plus the accrued interest. /// @return uint256 total amount function totalAssets() public view override returns (uint256) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.totalAssetsDeposited + _interestAccruedFromCache(); } @@ -511,7 +479,7 @@ contract FourSixTwoSixAgg is /// @dev the total assets allocatable is the amount of assets deposited into the aggregator + assets already deposited into strategies /// @return uint256 total assets function totalAssetsAllocatable() public view returns (uint256) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return IERC20(asset()).balanceOf(address(this)) + $.totalAllocated; } @@ -521,7 +489,7 @@ contract FourSixTwoSixAgg is function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { _callHooksTarget(DEPOSIT, caller); - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.totalAssetsDeposited += assets; @@ -538,7 +506,7 @@ contract FourSixTwoSixAgg is { _callHooksTarget(WITHDRAW, caller); - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); @@ -554,7 +522,7 @@ contract FourSixTwoSixAgg is // TODO: add access control function withdrawFromStrategy(address _strategy, uint256 _withdrawAmount) external { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); // Update allocated assets $.strategies[_strategy].allocated -= uint120(_withdrawAmount); @@ -588,7 +556,7 @@ contract FourSixTwoSixAgg is function _gulp() internal { _updateInterestAccrued(); - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); if ($.totalAssetsDeposited == 0) return; uint256 toGulp = totalAssetsAllocatable() - $.totalAssetsDeposited - $.interestLeft; @@ -605,7 +573,7 @@ contract FourSixTwoSixAgg is } function _harvest(address _strategy) internal { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); uint120 strategyAllocatedAmount = $.strategies[_strategy].allocated; @@ -645,7 +613,7 @@ contract FourSixTwoSixAgg is } function _accruePerformanceFee(address _strategy, uint256 _yield) internal returns (uint120) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); address cachedFeeRecipient = $.feeRecipient; uint256 cachedPerformanceFee = $.performanceFee; @@ -687,7 +655,7 @@ contract FourSixTwoSixAgg is /// @dev Get accrued interest without updating it. /// @return uint256 accrued interest function _interestAccruedFromCache() internal view returns (uint256) { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); // If distribution ended, full amount is accrued if (block.timestamp >= $.interestSmearEnd) { @@ -712,24 +680,18 @@ contract FourSixTwoSixAgg is /// @return The address of the message sender. function _msgSender() internal view override (ContextUpgradeable) returns (address) { address sender = msg.sender; - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); if (sender == address($.evc)) { - (sender,) = $.evc.getCurrentOnBehalfOfAccount(address(0)); + (sender,) = IEVC($.evc).getCurrentOnBehalfOfAccount(address(0)); } return sender; } function _lock() private onlyInitializing { - AggregationVaultStorage storage $ = _getAggregationVaultStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.locked = REENTRANCYLOCK__UNLOCKED; } - - function _getAggregationVaultStorage() private pure returns (AggregationVaultStorage storage $) { - assembly { - $.slot := AggregationVaultStorageLocation - } - } } diff --git a/src/Hooks.sol b/src/Hooks.sol index c73f3b28..a205262e 100644 --- a/src/Hooks.sol +++ b/src/Hooks.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +import {StorageLib, HooksStorage} from "./lib/StorageLib.sol"; import {HooksLib} from "./lib/HooksLib.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; @@ -20,21 +21,13 @@ abstract contract Hooks { uint256 constant HOOKS_MASK = 0x00000000000000000000000000000000000000000000000000000000FFFFFFFF; - struct HooksStorage { - /// @dev storing the hooks target and kooked functions. - uint256 hooksConfig; - } - - // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.Hooks")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant HooksStorageLocation = 0x7daefa3ee1567d8892b825e51ce683a058a5785193bcc2ca50940db02ccbf700; - event SetHooksConfig(address indexed hooksTarget, uint32 hookedFns); /// @notice Get the hooks contract and the hooked functions. /// @return address Hooks contract. /// @return uint32 Hooked functions. function getHooksConfig() external view returns (address, uint32) { - HooksStorage storage $ = _getHooksStorage(); + HooksStorage storage $ = StorageLib._getHooksStorage(); return _getHooksConfig($.hooksConfig); } @@ -59,7 +52,7 @@ abstract contract Hooks { } if (_hookedFns >= ACTIONS_COUNTER) revert InvalidHookedFns(); - HooksStorage storage $ = _getHooksStorage(); + HooksStorage storage $ = StorageLib._getHooksStorage(); $.hooksConfig = (uint256(uint160(_hooksTarget)) << 32) | uint256(_hookedFns); emit SetHooksConfig(_hooksTarget, _hookedFns); @@ -69,7 +62,7 @@ abstract contract Hooks { /// @param _fn Function to call the hook for. /// @param _caller Caller's address. function _callHooksTarget(uint32 _fn, address _caller) internal { - HooksStorage storage $ = _getHooksStorage(); + HooksStorage storage $ = StorageLib._getHooksStorage(); (address target, uint32 hookedFns) = _getHooksConfig($.hooksConfig); @@ -98,10 +91,4 @@ abstract contract Hooks { revert EmptyError(); } - - function _getHooksStorage() private pure returns (HooksStorage storage $) { - assembly { - $.slot := HooksStorageLocation - } - } } diff --git a/src/Rebalancer.sol b/src/Rebalancer.sol index 6c5f554a..e783d700 100644 --- a/src/Rebalancer.sol +++ b/src/Rebalancer.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; +import {IFourSixTwoSixAgg, Strategy} from "./interface/IFourSixTwoSixAgg.sol"; import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; contract Rebalancer { @@ -36,7 +36,7 @@ contract Rebalancer { IFourSixTwoSixAgg(_curatedVault).harvest(_strategy); - IFourSixTwoSixAgg.Strategy memory strategyData = IFourSixTwoSixAgg(_curatedVault).getStrategy(_strategy); + Strategy memory strategyData = IFourSixTwoSixAgg(_curatedVault).getStrategy(_strategy); uint256 totalAllocationPointsCache = IFourSixTwoSixAgg(_curatedVault).totalAllocationPoints(); uint256 totalAssetsAllocatableCache = IFourSixTwoSixAgg(_curatedVault).totalAssetsAllocatable(); diff --git a/src/interface/IFourSixTwoSixAgg.sol b/src/interface/IFourSixTwoSixAgg.sol index c8fdbc84..773b3d23 100644 --- a/src/interface/IFourSixTwoSixAgg.sol +++ b/src/interface/IFourSixTwoSixAgg.sol @@ -1,19 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -interface IFourSixTwoSixAgg { - /// @dev A struct that hold a strategy allocation's config - /// allocated: amount of asset deposited into strategy - /// allocationPoints: number of points allocated to this strategy - /// active: a boolean to indice if this strategy is active or not - /// cap: an optional cap in terms of deposited underlying asset. By default, it is set to 0(not activated) - struct Strategy { - uint120 allocated; - uint120 allocationPoints; - bool active; - uint120 cap; - } +import {Strategy} from "../lib/StorageLib.sol"; +interface IFourSixTwoSixAgg { function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external; function gulp() external; function harvest(address strategy) external; diff --git a/src/lib/StorageLib.sol b/src/lib/StorageLib.sol new file mode 100644 index 00000000..e3f00b68 --- /dev/null +++ b/src/lib/StorageLib.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +/// @custom:storage-location erc7201:euler_aggregation_vault.storage.AggregationVault +struct AggregationVaultStorage { + address evc; + /// @dev Total amount of _asset deposited into FourSixTwoSixAgg contract + uint256 totalAssetsDeposited; + /// @dev Total amount of _asset deposited across all strategies. + uint256 totalAllocated; + /// @dev Total amount of allocation points across all strategies including the cash reserve. + uint256 totalAllocationPoints; + /// @dev fee rate + uint256 performanceFee; + /// @dev fee recipient address + address feeRecipient; + /// @dev Withdrawal queue contract's address + address withdrawalQueue; + /// @dev Mapping between strategy address and it's allocation config + mapping(address => Strategy) strategies; + /// lastInterestUpdate: last timestamo where interest was updated. + uint40 lastInterestUpdate; + /// interestSmearEnd: timestamp when the smearing of interest end. + uint40 interestSmearEnd; + /// interestLeft: amount of interest left to smear. + uint168 interestLeft; + /// locked: if locked or not for update. + uint8 locked; +} +/// @custom:storage-location erc7201:euler_aggregation_vault.storage.BalanceForwarder + +struct BalanceForwarderStorage { + address balanceTracker; + mapping(address => bool) isBalanceForwarderEnabled; +} +/// @custom:storage-location erc7201:euler_aggregation_vault.storage.Hooks + +struct HooksStorage { + /// @dev storing the hooks target and kooked functions. + uint256 hooksConfig; +} +/// @dev A struct that hold a strategy allocation's config +/// allocated: amount of asset deposited into strategy +/// allocationPoints: number of points allocated to this strategy +/// active: a boolean to indice if this strategy is active or not +/// cap: an optional cap in terms of deposited underlying asset. By default, it is set to 0(not activated) + +struct Strategy { + uint120 allocated; + uint120 allocationPoints; + bool active; + uint120 cap; +} + +library StorageLib { + // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.AggregationVault")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant AggregationVaultStorageLocation = + 0x7da5ece5aff94f3377324d715703a012d3253da37511270103c646f171c0aa00; + // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.BalanceForwarder")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant BalanceForwarderStorageLocation = + 0xf0af7cc3986d6cac468ac54c07f5f08359b3a00d65f8e2a86f2676ec79073f00; + // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.Hooks")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant HooksStorageLocation = 0x7daefa3ee1567d8892b825e51ce683a058a5785193bcc2ca50940db02ccbf700; + + function _getAggregationVaultStorage() internal pure returns (AggregationVaultStorage storage $) { + assembly { + $.slot := AggregationVaultStorageLocation + } + } + + function _getBalanceForwarderStorage() internal pure returns (BalanceForwarderStorage storage $) { + assembly { + $.slot := BalanceForwarderStorageLocation + } + } + + function _getHooksStorage() internal pure returns (HooksStorage storage $) { + assembly { + $.slot := HooksStorageLocation + } + } +} diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 47f7e600..641c9122 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import "evk/test/unit/evault/EVaultTestBase.t.sol"; -import {FourSixTwoSixAgg} from "../../src/FourSixTwoSixAgg.sol"; +import {FourSixTwoSixAgg, Strategy} from "../../src/FourSixTwoSixAgg.sol"; import {Rebalancer} from "../../src/Rebalancer.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; import {Hooks} from "../../src/Hooks.sol"; @@ -61,7 +61,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { } function testInitialParams() public { - FourSixTwoSixAgg.Strategy memory cashReserve = fourSixTwoSixAgg.getStrategy(address(0)); + Strategy memory cashReserve = fourSixTwoSixAgg.getStrategy(address(0)); assertEq(cashReserve.allocated, 0); assertEq(cashReserve.allocationPoints, CASH_RESERVE_ALLOCATION_POINTS); diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 281e441f..4038be6b 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -9,7 +9,8 @@ import { IEVault, IRMTestDefault, TestERC20, - WithdrawalQueue + WithdrawalQueue, + Strategy } from "../common/FourSixTwoSixAggBase.t.sol"; contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { @@ -48,7 +49,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -71,7 +72,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // partial withdraw, no need to withdraw from strategy as cash reserve is enough uint256 amountToWithdraw = 6000e18; { - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); uint256 strategyShareBalanceBefore = eTST.balanceOf(address(fourSixTwoSixAgg)); uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); @@ -129,7 +130,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -216,9 +217,8 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - FourSixTwoSixAgg.Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); - FourSixTwoSixAgg.Strategy memory eTSTsecondarystrategyBefore = - fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); + Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory eTSTsecondarystrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTstrategyBefore.allocated); assertEq( @@ -316,7 +316,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -406,9 +406,8 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - FourSixTwoSixAgg.Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); - FourSixTwoSixAgg.Strategy memory eTSTsecondarystrategyBefore = - fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); + Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory eTSTsecondarystrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTstrategyBefore.allocated); assertEq( @@ -537,9 +536,8 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - FourSixTwoSixAgg.Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); - FourSixTwoSixAgg.Strategy memory eTSTsecondarystrategyBefore = - fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); + Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory eTSTsecondarystrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTstrategyBefore.allocated); assertEq( diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 0ecda9a0..5b3a811b 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -8,7 +8,8 @@ import { EVault, IEVault, IRMTestDefault, - TestERC20 + TestERC20, + Strategy } from "../common/FourSixTwoSixAggBase.t.sol"; contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { @@ -76,7 +77,7 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -108,7 +109,7 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { (, uint256 performanceFee) = fourSixTwoSixAgg.performanceFeeConfig(); uint256 expectedPerformanceFee = yield * performanceFee / 1e18; - FourSixTwoSixAgg.Strategy memory strategyBeforeHarvest = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBeforeHarvest = fourSixTwoSixAgg.getStrategy(address(eTST)); uint256 totalAllocatedBefore = fourSixTwoSixAgg.totalAllocated(); // harvest diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index e0235ecd..4da58520 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -8,7 +8,8 @@ import { EVault, IEVault, IRMTestDefault, - TestERC20 + TestERC20, + Strategy } from "../common/FourSixTwoSixAggBase.t.sol"; contract StrategyCapE2ETest is FourSixTwoSixAggBase { @@ -31,7 +32,7 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { vm.prank(manager); fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); - FourSixTwoSixAgg.Strategy memory strategy = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategy = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(strategy.cap, cap); } @@ -72,7 +73,7 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -132,7 +133,7 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); diff --git a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol index 11cfc703..07c38942 100644 --- a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol +++ b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg} from "../common/FourSixTwoSixAggBase.t.sol"; +import {FourSixTwoSixAggBase, FourSixTwoSixAgg, Strategy} from "../common/FourSixTwoSixAggBase.t.sol"; contract AdjustAllocationsPointsFuzzTest is FourSixTwoSixAggBase { function setUp() public virtual override { @@ -21,7 +21,7 @@ contract AdjustAllocationsPointsFuzzTest is FourSixTwoSixAggBase { vm.prank(manager); fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), _newAllocationPoints); - FourSixTwoSixAgg.Strategy memory strategy = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategy = fourSixTwoSixAgg.getStrategy(address(eTST)); if (_newAllocationPoints < strategyAllocationPoints) { assertEq( diff --git a/test/unit/AdjustAllocationPointsTest.t.sol b/test/unit/AdjustAllocationPointsTest.t.sol index 6e7f66fd..0dd6db38 100644 --- a/test/unit/AdjustAllocationPointsTest.t.sol +++ b/test/unit/AdjustAllocationPointsTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg} from "../common/FourSixTwoSixAggBase.t.sol"; +import {FourSixTwoSixAggBase, FourSixTwoSixAgg, Strategy} from "../common/FourSixTwoSixAggBase.t.sol"; contract AdjustAllocationsPointsTest is FourSixTwoSixAggBase { uint256 initialStrategyAllocationPoints = 500e18; @@ -20,7 +20,7 @@ contract AdjustAllocationsPointsTest is FourSixTwoSixAggBase { vm.prank(manager); fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), newAllocationPoints); - FourSixTwoSixAgg.Strategy memory strategy = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategy = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq( fourSixTwoSixAgg.totalAllocationPoints(), diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index a866267a..d6c383f1 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -1,7 +1,7 @@ // // SPDX-License-Identifier: GPL-2.0-or-later // pragma solidity ^0.8.0; -// import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault} from "../common/FourSixTwoSixAggBase.t.sol"; +// import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault, Strategy} from "../common/FourSixTwoSixAggBase.t.sol"; // contract GulpTest is FourSixTwoSixAggBase { // uint256 user1InitialBalance = 100000e18; @@ -36,7 +36,7 @@ // // rebalance into strategy // vm.warp(block.timestamp + 86400); // { -// FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); +// Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); // assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index 7ee54b8d..6b6efddb 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault} from "../common/FourSixTwoSixAggBase.t.sol"; +import { + FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault, Strategy +} from "../common/FourSixTwoSixAggBase.t.sol"; contract HarvestTest is FourSixTwoSixAggBase { uint256 user1InitialBalance = 100000e18; @@ -36,7 +38,7 @@ contract HarvestTest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -56,7 +58,7 @@ contract HarvestTest is FourSixTwoSixAggBase { function testHarvest() public { // no yield increase - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); uint256 totalAllocatedBefore = fourSixTwoSixAgg.totalAllocated(); assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) == strategyBefore.allocated); @@ -101,7 +103,7 @@ contract HarvestTest is FourSixTwoSixAggBase { abi.encode(aggrCurrentStrategyBalance * 9e17 / 1e18) ); - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated); @@ -121,7 +123,7 @@ contract HarvestTest is FourSixTwoSixAggBase { abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated); uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(fourSixTwoSixAgg)); @@ -164,7 +166,7 @@ contract HarvestTest is FourSixTwoSixAggBase { abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated); uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(fourSixTwoSixAgg)); uint256 user1SharesBefore = fourSixTwoSixAgg.balanceOf(user1); diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index 5342e9b0..23378e97 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -8,7 +8,8 @@ import { EVault, IEVault, IRMTestDefault, - TestERC20 + TestERC20, + Strategy } from "../common/FourSixTwoSixAggBase.t.sol"; contract RebalanceTest is FourSixTwoSixAggBase { @@ -46,7 +47,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -88,7 +89,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -154,7 +155,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into eTSTsecondary vm.warp(block.timestamp + 86400); { - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); assertEq( eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), @@ -205,7 +206,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into strategy vm.warp(block.timestamp + 86400); - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -260,7 +261,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { vm.warp(block.timestamp + 86400); - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); @@ -315,7 +316,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { vm.warp(block.timestamp + 86400); - FourSixTwoSixAgg.Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index d90ee41b..ea12437d 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg, IEVault} from "../common/FourSixTwoSixAggBase.t.sol"; +import {FourSixTwoSixAggBase, FourSixTwoSixAgg, IEVault, Strategy} from "../common/FourSixTwoSixAggBase.t.sol"; contract RemoveStrategyTest is FourSixTwoSixAggBase { uint256 strategyAllocationPoints; @@ -22,7 +22,7 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { vm.prank(manager); fourSixTwoSixAgg.removeStrategy(address(eTST)); - FourSixTwoSixAgg.Strategy memory strategyAfter = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyAfter = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(strategyAfter.active, false); assertEq(strategyAfter.allocationPoints, 0); @@ -42,7 +42,7 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { vm.prank(manager); fourSixTwoSixAgg.removeStrategy(address(eTST)); - FourSixTwoSixAgg.Strategy memory strategyAfter = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyAfter = fourSixTwoSixAgg.getStrategy(address(eTST)); assertEq(strategyAfter.active, false); assertEq(strategyAfter.allocationPoints, 0); From 9dd308021018a99cdee4838a49b90a770b8ea424 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 18 Jun 2024 10:47:26 +0300 Subject: [PATCH 173/316] working version --- src/Base.sol | 32 ++++++ src/Dispatch.sol | 59 ++++++++++ src/FourSixTwoSixAgg.sol | 128 ++++++--------------- src/FourSixTwoSixAggFactory.sol | 41 +++++-- src/Hooks.sol | 32 +++--- src/{BalanceForwarder.sol => Rewards.sol} | 78 ++++++++----- src/lib/ErrorsLib.sol | 22 ++++ test/common/FourSixTwoSixAggBase.t.sol | 24 +++- test/e2e/BalanceForwarderE2ETest.t.sol | 12 +- test/e2e/HooksE2ETest.t.sol | 8 +- test/e2e/StrategyCapE2ETest.t.sol | 5 +- test/unit/AdjustAllocationPointsTest.t.sol | 4 +- 12 files changed, 286 insertions(+), 159 deletions(-) create mode 100644 src/Base.sol create mode 100644 src/Dispatch.sol rename src/{BalanceForwarder.sol => Rewards.sol} (66%) create mode 100644 src/lib/ErrorsLib.sol diff --git a/src/Base.sol b/src/Base.sol new file mode 100644 index 00000000..9d86c15d --- /dev/null +++ b/src/Base.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; +import {ErrorsLib} from "./lib/ErrorsLib.sol"; +import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; + +contract Base { + uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; + uint8 internal constant REENTRANCYLOCK__LOCKED = 2; + + modifier nonReentrant() { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if ($.locked == REENTRANCYLOCK__LOCKED) revert ErrorsLib.Reentrancy(); + + $.locked = REENTRANCYLOCK__LOCKED; + _; + $.locked = REENTRANCYLOCK__UNLOCKED; + } + + function _msgSender() internal view virtual returns (address) { + address sender = msg.sender; + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if (sender == address($.evc)) { + (sender,) = IEVC($.evc).getCurrentOnBehalfOfAccount(address(0)); + } + + return sender; + } +} diff --git a/src/Dispatch.sol b/src/Dispatch.sol new file mode 100644 index 00000000..a61931ef --- /dev/null +++ b/src/Dispatch.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.0; + +import {Base} from "./Base.sol"; +import {HooksModule} from "./Hooks.sol"; +import {RewardsModule} from "./Rewards.sol"; + +abstract contract Dispatch is RewardsModule, HooksModule { + error E_Unauthorized(); + + address public immutable MODULE_REWARDS; + address public immutable MODULE_HOOKS; + + // /// @title DeployedModules + // /// @notice This struct is used to pass in the addresses of EVault modules during deployment + // struct DeployedModules { + // address initialize; + // address token; + // address vault; + // address borrowing; + // address liquidation; + // address riskManager; + // address balanceForwarder; + // address governance; + // } + + // constructor(Integrations memory integrations, DeployedModules memory modules) Base(integrations) { + // MODULE_INITIALIZE = AddressUtils.checkContract(modules.initialize); + // MODULE_TOKEN = AddressUtils.checkContract(modules.token); + // MODULE_VAULT = AddressUtils.checkContract(modules.vault); + // MODULE_BORROWING = AddressUtils.checkContract(modules.borrowing); + // MODULE_LIQUIDATION = AddressUtils.checkContract(modules.liquidation); + // MODULE_RISKMANAGER = AddressUtils.checkContract(modules.riskManager); + // MODULE_BALANCE_FORWARDER = AddressUtils.checkContract(modules.balanceForwarder); + // MODULE_GOVERNANCE = AddressUtils.checkContract(modules.governance); + // } + constructor(address _rewardsModule, address _hooksModule) Base() { + MODULE_REWARDS = _rewardsModule; + MODULE_HOOKS = _hooksModule; + } + + // Modifier proxies the function call to a module and low-level returns the result + modifier use(address module) { + _; // when using the modifier, it is assumed the function body is empty. + delegateToModule(module); + } + + function delegateToModule(address module) private { + assembly { + calldatacopy(0, 0, calldatasize()) + let result := delegatecall(gas(), module, 0, calldatasize(), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } +} diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 3e20cd25..b456bdef 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +import {Base} from "./Base.sol"; // external dep -import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgradeable.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { ERC4626Upgradeable, @@ -13,48 +13,24 @@ import { import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; // internal dep import {StorageLib, AggregationVaultStorage, Strategy} from "./lib/StorageLib.sol"; -import {Hooks} from "./Hooks.sol"; -import {BalanceForwarder, IBalanceForwarder, IBalanceTracker} from "./BalanceForwarder.sol"; +import {ErrorsLib} from "./lib/ErrorsLib.sol"; +import {IBalanceTracker} from "./Rewards.sol"; import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; +import {Dispatch} from "./Dispatch.sol"; +import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgradeable.sol"; /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol /// @dev inspired by Yearn v3 ❤️ -contract FourSixTwoSixAgg is - ERC4626Upgradeable, - AccessControlEnumerableUpgradeable, - BalanceForwarder, - Hooks, - IFourSixTwoSixAgg -{ +contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradeable, Dispatch, IFourSixTwoSixAgg { using SafeERC20 for IERC20; using SafeCast for uint256; - error Reentrancy(); - error ArrayLengthMismatch(); - error InitialAllocationPointsZero(); - error NegativeYield(); - error InactiveStrategy(); - error InvalidStrategyAsset(); - error StrategyAlreadyExist(); - error AlreadyRemoved(); - error PerformanceFeeAlreadySet(); - error MaxPerformanceFeeExceeded(); - error FeeRecipientNotSet(); - error FeeRecipientAlreadySet(); - error CanNotRemoveCashReserve(); - error DuplicateInitialStrategy(); - - uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; - uint8 internal constant REENTRANCYLOCK__LOCKED = 2; - // Roles bytes32 public constant STRATEGY_MANAGER = keccak256("STRATEGY_MANAGER"); bytes32 public constant STRATEGY_MANAGER_ADMIN = keccak256("STRATEGY_MANAGER_ADMIN"); @@ -73,8 +49,6 @@ contract FourSixTwoSixAgg is event SetFeeRecipient(address indexed oldRecipient, address indexed newRecipient); event SetPerformanceFee(uint256 oldFee, uint256 newFee); - event OptInStrategyRewards(address indexed strategy); - event OptOutStrategyRewards(address indexed strategy); event Gulp(uint256 interestLeft, uint256 interestSmearEnd); event Harvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); event AdjustAllocationPoints(address indexed strategy, uint256 oldPoints, uint256 newPoints); @@ -84,16 +58,7 @@ contract FourSixTwoSixAgg is event SetStrategyCap(address indexed strategy, uint256 cap); event Rebalance(address indexed strategy, uint256 _amountToRebalance, bool _isDeposit); - /// @dev Non reentrancy modifier for interest rate updates - modifier nonReentrant() { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - if ($.locked == REENTRANCYLOCK__LOCKED) revert Reentrancy(); - - $.locked = REENTRANCYLOCK__LOCKED; - _; - $.locked = REENTRANCYLOCK__UNLOCKED; - } + constructor(address _rewardsModule, address _hooksModule) Dispatch(_rewardsModule, _hooksModule) {} // /// @param _evc EVC address // /// @param _asset Aggregator's asset address @@ -118,7 +83,7 @@ contract FourSixTwoSixAgg is _lock(); - if (_initialCashAllocationPoints == 0) revert InitialAllocationPointsZero(); + if (_initialCashAllocationPoints == 0) revert ErrorsLib.InitialAllocationPointsZero(); AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.withdrawalQueue = _withdrawalQueue; @@ -148,7 +113,7 @@ contract FourSixTwoSixAgg is AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); address feeRecipientCached = $.feeRecipient; - if (_newFeeRecipient == feeRecipientCached) revert FeeRecipientAlreadySet(); + if (_newFeeRecipient == feeRecipientCached) revert ErrorsLib.FeeRecipientAlreadySet(); emit SetFeeRecipient(feeRecipientCached, _newFeeRecipient); @@ -162,9 +127,9 @@ contract FourSixTwoSixAgg is uint256 performanceFeeCached = $.performanceFee; - if (_newFee > MAX_PERFORMANCE_FEE) revert MaxPerformanceFeeExceeded(); - if ($.feeRecipient == address(0)) revert FeeRecipientNotSet(); - if (_newFee == performanceFeeCached) revert PerformanceFeeAlreadySet(); + if (_newFee > MAX_PERFORMANCE_FEE) revert ErrorsLib.MaxPerformanceFeeExceeded(); + if ($.feeRecipient == address(0)) revert ErrorsLib.FeeRecipientNotSet(); + if (_newFee == performanceFeeCached) revert ErrorsLib.PerformanceFeeAlreadySet(); emit SetPerformanceFee(performanceFeeCached, _newFee); @@ -173,23 +138,11 @@ contract FourSixTwoSixAgg is /// @notice Opt in to strategy rewards /// @param _strategy Strategy address - function optInStrategyRewards(address _strategy) external onlyRole(MANAGER) nonReentrant { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - if (!$.strategies[_strategy].active) revert InactiveStrategy(); - - IBalanceForwarder(_strategy).enableBalanceForwarder(); - - emit OptInStrategyRewards(_strategy); - } + function optInStrategyRewards(address _strategy) external override onlyRole(MANAGER) use(MODULE_REWARDS) {} /// @notice Opt out of strategy rewards /// @param _strategy Strategy address - function optOutStrategyRewards(address _strategy) external onlyRole(MANAGER) nonReentrant { - IBalanceForwarder(_strategy).disableBalanceForwarder(); - - emit OptOutStrategyRewards(_strategy); - } + function optOutStrategyRewards(address _strategy) external override onlyRole(MANAGER) use(MODULE_REWARDS) {} /// @notice Claim a specific strategy rewards /// @param _strategy Strategy address. @@ -198,13 +151,10 @@ contract FourSixTwoSixAgg is /// @param _forfeitRecentReward Whether to forfeit the recent rewards and not update the accumulator. function claimStrategyReward(address _strategy, address _reward, address _recipient, bool _forfeitRecentReward) external + override onlyRole(MANAGER) - nonReentrant - { - address rewardStreams = IBalanceForwarder(_strategy).balanceTrackerAddress(); - - IRewardStreams(rewardStreams).claimReward(_strategy, _reward, _recipient, _forfeitRecentReward); - } + use(MODULE_REWARDS) + {} /// @notice Enables balance forwarding for sender /// @dev Should call the IBalanceTracker hook with the current user's balance @@ -217,9 +167,7 @@ contract FourSixTwoSixAgg is /// @notice Disables balance forwarding for the sender /// @dev Should call the IBalanceTracker hook with the account's balance of 0 - function disableBalanceForwarder() external override nonReentrant { - _disableBalanceForwarder(_msgSender()); - } + function disableBalanceForwarder() external override use(MODULE_REWARDS) {} /// @notice Harvest strategy. /// @param strategy address of strategy @@ -277,7 +225,7 @@ contract FourSixTwoSixAgg is Strategy memory strategyDataCache = $.strategies[_strategy]; if (!strategyDataCache.active) { - revert InactiveStrategy(); + revert ErrorsLib.InactiveStrategy(); } $.strategies[_strategy].allocationPoints = _newPoints.toUint120(); @@ -294,7 +242,7 @@ contract FourSixTwoSixAgg is AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); if (!$.strategies[_strategy].active) { - revert InactiveStrategy(); + revert ErrorsLib.InactiveStrategy(); } $.strategies[_strategy].cap = _cap.toUint120(); @@ -310,11 +258,11 @@ contract FourSixTwoSixAgg is AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); if ($.strategies[_strategy].active) { - revert StrategyAlreadyExist(); + revert ErrorsLib.StrategyAlreadyExist(); } if (IERC4626(_strategy).asset() != asset()) { - revert InvalidStrategyAsset(); + revert ErrorsLib.InvalidStrategyAsset(); } _callHooksTarget(ADD_STRATEGY, _msgSender()); @@ -333,14 +281,14 @@ contract FourSixTwoSixAgg is /// @dev Can only be called by an address that have the STRATEGY_REMOVER /// @param _strategy Address of the strategy function removeStrategy(address _strategy) external nonReentrant onlyRole(STRATEGY_REMOVER) { - if (_strategy == address(0)) revert CanNotRemoveCashReserve(); + if (_strategy == address(0)) revert ErrorsLib.CanNotRemoveCashReserve(); AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); Strategy storage strategyStorage = $.strategies[_strategy]; if (!strategyStorage.active) { - revert AlreadyRemoved(); + revert ErrorsLib.AlreadyRemoved(); } _callHooksTarget(REMOVE_STRATEGY, _msgSender()); @@ -450,9 +398,12 @@ contract FourSixTwoSixAgg is /// @dev This funtion should be overriden to implement access control. /// @param _hooksTarget Hooks contract. /// @param _hookedFns Hooked functions. - function setHooksConfig(address _hooksTarget, uint32 _hookedFns) public override onlyRole(MANAGER) nonReentrant { - _setHooksConfig(_hooksTarget, _hookedFns); - } + function setHooksConfig(address _hooksTarget, uint32 _hookedFns) + external + override + onlyRole(MANAGER) + use(MODULE_HOOKS) + {} /// @notice update accrued interest. function _updateInterestAccrued() internal { @@ -674,24 +625,13 @@ contract FourSixTwoSixAgg is return $.interestLeft * timePassed / totalDuration; } - /// @notice Retrieves the message sender in the context of the EVC. - /// @dev This function returns the account on behalf of which the current operation is being performed, which is - /// either msg.sender or the account authenticated by the EVC. - /// @return The address of the message sender. - function _msgSender() internal view override (ContextUpgradeable) returns (address) { - address sender = msg.sender; - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - if (sender == address($.evc)) { - (sender,) = IEVC($.evc).getCurrentOnBehalfOfAccount(address(0)); - } - - return sender; - } - function _lock() private onlyInitializing { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.locked = REENTRANCYLOCK__UNLOCKED; } + + function _msgSender() internal view override (ContextUpgradeable, Base) returns (address) { + return Base._msgSender(); + } } diff --git a/src/FourSixTwoSixAggFactory.sol b/src/FourSixTwoSixAggFactory.sol index e06a99c3..34e67bee 100644 --- a/src/FourSixTwoSixAggFactory.sol +++ b/src/FourSixTwoSixAggFactory.sol @@ -1,18 +1,40 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAgg} from "./FourSixTwoSixAgg.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Rewards} from "./Rewards.sol"; +import {Hooks} from "./Hooks.sol"; import {WithdrawalQueue} from "./WithdrawalQueue.sol"; +import {FourSixTwoSixAgg} from "./FourSixTwoSixAgg.sol"; contract FourSixTwoSixAggFactory { + // core dependencies address public immutable evc; address public immutable balanceTracker; + // core modules + address public immutable rewardsImpl; + address public immutable hooksImpl; + // peripheries + /// @dev Rebalancer periphery, one instance can serve different aggregation vaults address public immutable rebalancer; + /// @dev Withdrawal queue perihperhy, need to be deployed per aggregation vault + address public immutable withdrawalQueueImpl; - constructor(address _evc, address _balanceTracker, address _rebalancer) { + constructor( + address _evc, + address _balanceTracker, + address _rewardsImpl, + address _hooksImpl, + address _rebalancer, + address _withdrawalQueueImpl + ) { evc = _evc; balanceTracker = _balanceTracker; + rewardsImpl = _rewardsImpl; + hooksImpl = _hooksImpl; + rebalancer = _rebalancer; + withdrawalQueueImpl = _withdrawalQueueImpl; } // TODO: decrease bytecode size, use clones or something @@ -22,18 +44,23 @@ contract FourSixTwoSixAggFactory { string memory _symbol, uint256 _initialCashAllocationPoints ) external returns (address) { - WithdrawalQueue withdrawalQueue = new WithdrawalQueue(); - FourSixTwoSixAgg fourSixTwoSixAgg = new FourSixTwoSixAgg(); + // cloning core modules + address rewardsImplAddr = Clones.clone(rewardsImpl); + Hooks hooks = Hooks(Clones.clone(hooksImpl)); + + // cloning peripheries + WithdrawalQueue withdrawalQueue = WithdrawalQueue(Clones.clone(withdrawalQueueImpl)); - address owner = msg.sender; + // deploy new aggregation vault + FourSixTwoSixAgg fourSixTwoSixAgg = new FourSixTwoSixAgg(rewardsImplAddr, address(hooks)); - withdrawalQueue.init(owner, address(fourSixTwoSixAgg)); + withdrawalQueue.init(msg.sender, address(fourSixTwoSixAgg)); fourSixTwoSixAgg.init( evc, balanceTracker, address(withdrawalQueue), rebalancer, - owner, + msg.sender, _asset, _name, _symbol, diff --git a/src/Hooks.sol b/src/Hooks.sol index a205262e..bd03fc55 100644 --- a/src/Hooks.sol +++ b/src/Hooks.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +import {Base} from "./Base.sol"; import {StorageLib, HooksStorage} from "./lib/StorageLib.sol"; import {HooksLib} from "./lib/HooksLib.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; -abstract contract Hooks { +abstract contract HooksModule is Base { using HooksLib for uint32; error InvalidHooksTarget(); @@ -23,26 +24,10 @@ abstract contract Hooks { event SetHooksConfig(address indexed hooksTarget, uint32 hookedFns); - /// @notice Get the hooks contract and the hooked functions. - /// @return address Hooks contract. - /// @return uint32 Hooked functions. - function getHooksConfig() external view returns (address, uint32) { - HooksStorage storage $ = StorageLib._getHooksStorage(); - - return _getHooksConfig($.hooksConfig); - } - /// @notice Set hooks contract and hooked functions. - /// @dev This funtion should be overriden to implement access control and call _setHooksConfig(). /// @param _hooksTarget Hooks contract. /// @param _hookedFns Hooked functions. - function setHooksConfig(address _hooksTarget, uint32 _hookedFns) public virtual; - - /// @notice Set hooks contract and hooked functions. - /// @dev This funtion should be called when implementing setHooksConfig(). - /// @param _hooksTarget Hooks contract. - /// @param _hookedFns Hooked functions. - function _setHooksConfig(address _hooksTarget, uint32 _hookedFns) internal { + function setHooksConfig(address _hooksTarget, uint32 _hookedFns) external virtual nonReentrant { if (_hooksTarget != address(0) && IHookTarget(_hooksTarget).isHookTarget() != IHookTarget.isHookTarget.selector) { revert NotHooksContract(); @@ -58,6 +43,15 @@ abstract contract Hooks { emit SetHooksConfig(_hooksTarget, _hookedFns); } + /// @notice Get the hooks contract and the hooked functions. + /// @return address Hooks contract. + /// @return uint32 Hooked functions. + function getHooksConfig() external view returns (address, uint32) { + HooksStorage storage $ = StorageLib._getHooksStorage(); + + return _getHooksConfig($.hooksConfig); + } + /// @notice Checks whether a hook has been installed for the function and if so, invokes the hook target. /// @param _fn Function to call the hook for. /// @param _caller Caller's address. @@ -92,3 +86,5 @@ abstract contract Hooks { revert EmptyError(); } } + +contract Hooks is HooksModule {} diff --git a/src/BalanceForwarder.sol b/src/Rewards.sol similarity index 66% rename from src/BalanceForwarder.sol rename to src/Rewards.sol index 687302c8..f6a029c1 100644 --- a/src/BalanceForwarder.sol +++ b/src/Rewards.sol @@ -1,39 +1,74 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +import {Base} from "./Base.sol"; import {StorageLib, BalanceForwarderStorage, AggregationVaultStorage} from "./lib/StorageLib.sol"; import {IBalanceForwarder} from "./interface/IBalanceForwarder.sol"; import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; +import {ErrorsLib} from "./lib/ErrorsLib.sol"; +import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; /// @title BalanceForwarder contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) /// @notice A generic contract to integrate with https://github.com/euler-xyz/reward-streams -abstract contract BalanceForwarderModule is IBalanceForwarder { +abstract contract RewardsModule is IBalanceForwarder, Base { using StorageLib for *; - error NotSupported(); - error AlreadyEnabled(); - error AlreadyDisabled(); - + event OptInStrategyRewards(address indexed strategy); + event OptOutStrategyRewards(address indexed strategy); event EnableBalanceForwarder(address indexed _user); event DisableBalanceForwarder(address indexed _user); + /// @notice Opt in to strategy rewards + /// @param _strategy Strategy address + function optInStrategyRewards(address _strategy) external virtual nonReentrant { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if (!$.strategies[_strategy].active) revert ErrorsLib.InactiveStrategy(); + + IBalanceForwarder(_strategy).enableBalanceForwarder(); + + emit OptInStrategyRewards(_strategy); + } + + /// @notice Opt out of strategy rewards + /// @param _strategy Strategy address + function optOutStrategyRewards(address _strategy) external virtual nonReentrant { + IBalanceForwarder(_strategy).disableBalanceForwarder(); + + emit OptOutStrategyRewards(_strategy); + } + + /// @notice Claim a specific strategy rewards + /// @param _strategy Strategy address. + /// @param _reward The address of the reward token. + /// @param _recipient The address to receive the claimed reward tokens. + /// @param _forfeitRecentReward Whether to forfeit the recent rewards and not update the accumulator. + function claimStrategyReward(address _strategy, address _reward, address _recipient, bool _forfeitRecentReward) + external + virtual + nonReentrant + { + address rewardStreams = IBalanceForwarder(_strategy).balanceTrackerAddress(); + + IRewardStreams(rewardStreams).claimReward(_strategy, _reward, _recipient, _forfeitRecentReward); + } + /// @notice Enables balance forwarding for the authenticated account /// @dev Only the authenticated account can enable balance forwarding for itself /// @dev Should call the IBalanceTracker hook with the current account's balance - function enableBalanceForwarder() external override { - address user = _msgSender(); - uint256 userBalance = this.balanceOf(user); - - _enableBalanceForwarder(user, userBalance); + function enableBalanceForwarder() external virtual { + return; } /// @notice Disables balance forwarding for the authenticated account /// @dev Only the authenticated account can disable balance forwarding for itself /// @dev Should call the IBalanceTracker hook with the account's balance of 0 - function disableBalanceForwarder() external virtual; + function disableBalanceForwarder() external virtual nonReentrant { + _disableBalanceForwarder(_msgSender()); + } /// @notice Retrieve the address of rewards contract, tracking changes in account's balances /// @return The balance tracker address @@ -54,8 +89,8 @@ abstract contract BalanceForwarderModule is IBalanceForwarder { BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); IBalanceTracker balanceTrackerCached = IBalanceTracker($.balanceTracker); - if (address(balanceTrackerCached) == address(0)) revert NotSupported(); - if ($.isBalanceForwarderEnabled[_sender]) revert AlreadyEnabled(); + if (address(balanceTrackerCached) == address(0)) revert ErrorsLib.NotSupported(); + if ($.isBalanceForwarderEnabled[_sender]) revert ErrorsLib.AlreadyEnabled(); $.isBalanceForwarderEnabled[_sender] = true; balanceTrackerCached.balanceTrackerHook(_sender, _senderBalance, false); @@ -70,8 +105,8 @@ abstract contract BalanceForwarderModule is IBalanceForwarder { BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); IBalanceTracker balanceTrackerCached = IBalanceTracker($.balanceTracker); - if (address(balanceTrackerCached) == address(0)) revert NotSupported(); - if (!$.isBalanceForwarderEnabled[_sender]) revert AlreadyDisabled(); + if (address(balanceTrackerCached) == address(0)) revert ErrorsLib.NotSupported(); + if (!$.isBalanceForwarderEnabled[_sender]) revert ErrorsLib.AlreadyDisabled(); $.isBalanceForwarderEnabled[_sender] = false; balanceTrackerCached.balanceTrackerHook(_sender, 0, false); @@ -101,17 +136,6 @@ abstract contract BalanceForwarderModule is IBalanceForwarder { return address($.balanceTracker); } - - function _msgSender() internal view override returns (address) { - address sender = msg.sender; - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - if (sender == address($.evc)) { - (sender,) = IEVC($.evc).getCurrentOnBehalfOfAccount(address(0)); - } - - return sender; - } } -contract BalanceForwarder is BalanceForwarderModule {} \ No newline at end of file +contract Rewards is RewardsModule {} diff --git a/src/lib/ErrorsLib.sol b/src/lib/ErrorsLib.sol new file mode 100644 index 00000000..3b57e3b8 --- /dev/null +++ b/src/lib/ErrorsLib.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +library ErrorsLib { + error Reentrancy(); + error ArrayLengthMismatch(); + error InitialAllocationPointsZero(); + error NegativeYield(); + error InactiveStrategy(); + error InvalidStrategyAsset(); + error StrategyAlreadyExist(); + error AlreadyRemoved(); + error PerformanceFeeAlreadySet(); + error MaxPerformanceFeeExceeded(); + error FeeRecipientNotSet(); + error FeeRecipientAlreadySet(); + error CanNotRemoveCashReserve(); + error DuplicateInitialStrategy(); + error NotSupported(); + error AlreadyEnabled(); + error AlreadyDisabled(); +} diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 641c9122..6ea444b8 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -5,10 +5,12 @@ import "evk/test/unit/evault/EVaultTestBase.t.sol"; import {FourSixTwoSixAgg, Strategy} from "../../src/FourSixTwoSixAgg.sol"; import {Rebalancer} from "../../src/Rebalancer.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; -import {Hooks} from "../../src/Hooks.sol"; +import {Hooks, HooksModule} from "../../src/Hooks.sol"; +import {Rewards} from "../../src/Rewards.sol"; import {FourSixTwoSixAggFactory} from "../../src/FourSixTwoSixAggFactory.sol"; import {WithdrawalQueue} from "../../src/WithdrawalQueue.sol"; import {IWithdrawalQueue} from "../../src/interface/IWithdrawalQueue.sol"; +import {ErrorsLib} from "../../src/lib/ErrorsLib.sol"; contract FourSixTwoSixAggBase is EVaultTestBase { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; @@ -18,8 +20,14 @@ contract FourSixTwoSixAggBase is EVaultTestBase { address user2; address manager; - FourSixTwoSixAggFactory fourSixTwoSixAggFactory; + // core modules + Rewards rewardsImpl; + Hooks hooksImpl; + // peripheries Rebalancer rebalancer; + WithdrawalQueue withdrawalQueueImpl; + + FourSixTwoSixAggFactory fourSixTwoSixAggFactory; FourSixTwoSixAgg fourSixTwoSixAgg; WithdrawalQueue withdrawalQueue; @@ -32,8 +40,18 @@ contract FourSixTwoSixAggBase is EVaultTestBase { user2 = makeAddr("User_2"); vm.startPrank(deployer); + rewardsImpl = new Rewards(); + hooksImpl = new Hooks(); rebalancer = new Rebalancer(); - fourSixTwoSixAggFactory = new FourSixTwoSixAggFactory(address(evc), address(0), address(rebalancer)); + withdrawalQueueImpl = new WithdrawalQueue(); + fourSixTwoSixAggFactory = new FourSixTwoSixAggFactory( + address(evc), + address(0), + address(rewardsImpl), + address(hooksImpl), + address(rebalancer), + address(withdrawalQueueImpl) + ); fourSixTwoSixAgg = FourSixTwoSixAgg( fourSixTwoSixAggFactory.deployEulerAggregationLayer( diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 024ff17d..2120fb80 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -9,7 +9,8 @@ import { IEVault, IRMTestDefault, TestERC20, - FourSixTwoSixAggFactory + FourSixTwoSixAggFactory, + Rewards } from "../common/FourSixTwoSixAggBase.t.sol"; import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol"; @@ -24,7 +25,14 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { vm.startPrank(deployer); trackingReward = address(new TrackingRewardStreams(address(evc), 2 weeks)); - fourSixTwoSixAggFactory = new FourSixTwoSixAggFactory(address(evc), trackingReward, address(rebalancer)); + fourSixTwoSixAggFactory = new FourSixTwoSixAggFactory( + address(evc), + trackingReward, + address(rewardsImpl), + address(hooksImpl), + address(rebalancer), + address(withdrawalQueueImpl) + ); fourSixTwoSixAgg = FourSixTwoSixAgg( fourSixTwoSixAggFactory.deployEulerAggregationLayer( address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS diff --git a/test/e2e/HooksE2ETest.t.sol b/test/e2e/HooksE2ETest.t.sol index efff9207..a0e966c0 100644 --- a/test/e2e/HooksE2ETest.t.sol +++ b/test/e2e/HooksE2ETest.t.sol @@ -10,7 +10,7 @@ import { IRMTestDefault, TestERC20, IHookTarget, - Hooks + HooksModule } from "../common/FourSixTwoSixAggBase.t.sol"; contract HooksE2ETest is FourSixTwoSixAggBase { @@ -45,7 +45,7 @@ contract HooksE2ETest is FourSixTwoSixAggBase { | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); vm.startPrank(manager); - vm.expectRevert(Hooks.InvalidHooksTarget.selector); + vm.expectRevert(HooksModule.InvalidHooksTarget.selector); fourSixTwoSixAgg.setHooksConfig(address(0), expectedHookedFns); vm.stopPrank(); } @@ -56,7 +56,7 @@ contract HooksE2ETest is FourSixTwoSixAggBase { vm.startPrank(manager); address hooksContract = address(new NotHooksContract()); - vm.expectRevert(Hooks.NotHooksContract.selector); + vm.expectRevert(HooksModule.NotHooksContract.selector); fourSixTwoSixAgg.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); } @@ -65,7 +65,7 @@ contract HooksE2ETest is FourSixTwoSixAggBase { uint32 expectedHookedFns = 1 << 5; vm.startPrank(manager); address hooksContract = address(new HooksContract()); - vm.expectRevert(Hooks.InvalidHookedFns.selector); + vm.expectRevert(HooksModule.InvalidHookedFns.selector); fourSixTwoSixAgg.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); } diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index 4da58520..d7373ad4 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -9,7 +9,8 @@ import { IEVault, IRMTestDefault, TestERC20, - Strategy + Strategy, + ErrorsLib } from "../common/FourSixTwoSixAggBase.t.sol"; contract StrategyCapE2ETest is FourSixTwoSixAggBase { @@ -41,7 +42,7 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { uint256 cap = 1000000e18; vm.prank(manager); - vm.expectRevert(FourSixTwoSixAgg.InactiveStrategy.selector); + vm.expectRevert(ErrorsLib.InactiveStrategy.selector); fourSixTwoSixAgg.setStrategyCap(address(0x2), cap); } diff --git a/test/unit/AdjustAllocationPointsTest.t.sol b/test/unit/AdjustAllocationPointsTest.t.sol index 0dd6db38..12cf042b 100644 --- a/test/unit/AdjustAllocationPointsTest.t.sol +++ b/test/unit/AdjustAllocationPointsTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg, Strategy} from "../common/FourSixTwoSixAggBase.t.sol"; +import {FourSixTwoSixAggBase, FourSixTwoSixAgg, Strategy, ErrorsLib} from "../common/FourSixTwoSixAggBase.t.sol"; contract AdjustAllocationsPointsTest is FourSixTwoSixAggBase { uint256 initialStrategyAllocationPoints = 500e18; @@ -43,7 +43,7 @@ contract AdjustAllocationsPointsTest is FourSixTwoSixAggBase { uint256 newAllocationPoints = 859e18; vm.startPrank(manager); - vm.expectRevert(FourSixTwoSixAgg.InactiveStrategy.selector); + vm.expectRevert(ErrorsLib.InactiveStrategy.selector); fourSixTwoSixAgg.adjustAllocationPoints(address(eTST2), newAllocationPoints); vm.stopPrank(); } From 529aad6f3ef99da3da1428f590abaa7d75bae896 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 18 Jun 2024 11:01:59 +0300 Subject: [PATCH 174/316] working version --- src/WithdrawalQueue.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WithdrawalQueue.sol b/src/WithdrawalQueue.sol index 56b7f873..1dd7730b 100644 --- a/src/WithdrawalQueue.sol +++ b/src/WithdrawalQueue.sol @@ -113,7 +113,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { /// @param _index1 index of first strategy /// @param _index2 index of second strategy function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external onlyRole(WITHDRAW_QUEUE_MANAGER) { - WithdrawalQueueStorage memory $ = _getWithdrawalQueueStorage(); + WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); uint256 length = $.withdrawalQueue.length; if (_index1 >= length || _index2 >= length) { From d6cbb5754ead98494b7ea84c21fb2598ce9fe3ef Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 18 Jun 2024 12:05:22 +0300 Subject: [PATCH 175/316] working version --- src/Base.sol | 32 ------ src/Dispatch.sol | 33 ++---- src/FourSixTwoSixAgg.sol | 65 +++++------- src/FourSixTwoSixAggFactory.sol | 21 ++-- src/Hooks.sol | 90 ---------------- src/Rewards.sol | 141 ------------------------- test/common/FourSixTwoSixAggBase.t.sol | 9 +- test/e2e/BalanceForwarderE2ETest.t.sol | 1 + 8 files changed, 52 insertions(+), 340 deletions(-) delete mode 100644 src/Base.sol delete mode 100644 src/Hooks.sol delete mode 100644 src/Rewards.sol diff --git a/src/Base.sol b/src/Base.sol deleted file mode 100644 index 9d86c15d..00000000 --- a/src/Base.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; -import {ErrorsLib} from "./lib/ErrorsLib.sol"; -import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; - -contract Base { - uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; - uint8 internal constant REENTRANCYLOCK__LOCKED = 2; - - modifier nonReentrant() { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - if ($.locked == REENTRANCYLOCK__LOCKED) revert ErrorsLib.Reentrancy(); - - $.locked = REENTRANCYLOCK__LOCKED; - _; - $.locked = REENTRANCYLOCK__UNLOCKED; - } - - function _msgSender() internal view virtual returns (address) { - address sender = msg.sender; - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - if (sender == address($.evc)) { - (sender,) = IEVC($.evc).getCurrentOnBehalfOfAccount(address(0)); - } - - return sender; - } -} diff --git a/src/Dispatch.sol b/src/Dispatch.sol index a61931ef..60e96a49 100644 --- a/src/Dispatch.sol +++ b/src/Dispatch.sol @@ -2,42 +2,21 @@ pragma solidity ^0.8.0; -import {Base} from "./Base.sol"; -import {HooksModule} from "./Hooks.sol"; -import {RewardsModule} from "./Rewards.sol"; +import {Shared} from "./Shared.sol"; +import {HooksModule} from "./modules/Hooks.sol"; +import {RewardsModule} from "./modules/Rewards.sol"; abstract contract Dispatch is RewardsModule, HooksModule { error E_Unauthorized(); address public immutable MODULE_REWARDS; address public immutable MODULE_HOOKS; + address public immutable MODULE_FEE; - // /// @title DeployedModules - // /// @notice This struct is used to pass in the addresses of EVault modules during deployment - // struct DeployedModules { - // address initialize; - // address token; - // address vault; - // address borrowing; - // address liquidation; - // address riskManager; - // address balanceForwarder; - // address governance; - // } - - // constructor(Integrations memory integrations, DeployedModules memory modules) Base(integrations) { - // MODULE_INITIALIZE = AddressUtils.checkContract(modules.initialize); - // MODULE_TOKEN = AddressUtils.checkContract(modules.token); - // MODULE_VAULT = AddressUtils.checkContract(modules.vault); - // MODULE_BORROWING = AddressUtils.checkContract(modules.borrowing); - // MODULE_LIQUIDATION = AddressUtils.checkContract(modules.liquidation); - // MODULE_RISKMANAGER = AddressUtils.checkContract(modules.riskManager); - // MODULE_BALANCE_FORWARDER = AddressUtils.checkContract(modules.balanceForwarder); - // MODULE_GOVERNANCE = AddressUtils.checkContract(modules.governance); - // } - constructor(address _rewardsModule, address _hooksModule) Base() { + constructor(address _rewardsModule, address _hooksModule, address _feeModule) Shared() { MODULE_REWARDS = _rewardsModule; MODULE_HOOKS = _hooksModule; + MODULE_FEE = _feeModule; } // Modifier proxies the function call to a module and low-level returns the result diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index b456bdef..f109edf8 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {Base} from "./Base.sol"; +import {Shared} from "./Shared.sol"; // external dep import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { @@ -17,7 +17,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; // internal dep import {StorageLib, AggregationVaultStorage, Strategy} from "./lib/StorageLib.sol"; import {ErrorsLib} from "./lib/ErrorsLib.sol"; -import {IBalanceTracker} from "./Rewards.sol"; +import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; import {Dispatch} from "./Dispatch.sol"; @@ -43,12 +43,8 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea bytes32 public constant REBALANCER = keccak256("REBALANCER"); bytes32 public constant REBALANCER_ADMIN = keccak256("REBALANCER_ADMIN"); - /// @dev The maximum performanceFee the vault can have is 50% - uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; uint256 public constant INTEREST_SMEAR = 2 weeks; - event SetFeeRecipient(address indexed oldRecipient, address indexed newRecipient); - event SetPerformanceFee(uint256 oldFee, uint256 newFee); event Gulp(uint256 interestLeft, uint256 interestSmearEnd); event Harvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); event AdjustAllocationPoints(address indexed strategy, uint256 oldPoints, uint256 newPoints); @@ -58,8 +54,21 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea event SetStrategyCap(address indexed strategy, uint256 cap); event Rebalance(address indexed strategy, uint256 _amountToRebalance, bool _isDeposit); - constructor(address _rewardsModule, address _hooksModule) Dispatch(_rewardsModule, _hooksModule) {} + constructor(address _rewardsModule, address _hooksModule, address _feeModule) + Dispatch(_rewardsModule, _hooksModule, _feeModule) + {} + struct InitParams { + address evc; + address balanceTracker; + address withdrawalQueuePeriphery; + address rebalancerPerihpery; + address aggregationVaultOwner; + address asset; + string name; + string symbol; + uint256 initialCashAllocationPoints; + } // /// @param _evc EVC address // /// @param _asset Aggregator's asset address // /// @param _name Aggregator's name @@ -109,32 +118,11 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea /// @notice Set performance fee recipient address /// @notice @param _newFeeRecipient Recipient address - function setFeeRecipient(address _newFeeRecipient) external onlyRole(MANAGER) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - address feeRecipientCached = $.feeRecipient; - - if (_newFeeRecipient == feeRecipientCached) revert ErrorsLib.FeeRecipientAlreadySet(); - - emit SetFeeRecipient(feeRecipientCached, _newFeeRecipient); - - $.feeRecipient = _newFeeRecipient; - } + function setFeeRecipient(address _newFeeRecipient) external onlyRole(MANAGER) use(MODULE_FEE) {} /// @notice Set performance fee (1e18 == 100%) /// @notice @param _newFee Fee rate - function setPerformanceFee(uint256 _newFee) external onlyRole(MANAGER) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - uint256 performanceFeeCached = $.performanceFee; - - if (_newFee > MAX_PERFORMANCE_FEE) revert ErrorsLib.MaxPerformanceFeeExceeded(); - if ($.feeRecipient == address(0)) revert ErrorsLib.FeeRecipientNotSet(); - if (_newFee == performanceFeeCached) revert ErrorsLib.PerformanceFeeAlreadySet(); - - emit SetPerformanceFee(performanceFeeCached, _newFee); - - $.performanceFee = _newFee; - } + function setPerformanceFee(uint256 _newFee) external onlyRole(MANAGER) use(MODULE_FEE) {} /// @notice Opt in to strategy rewards /// @param _strategy Strategy address @@ -158,12 +146,7 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea /// @notice Enables balance forwarding for sender /// @dev Should call the IBalanceTracker hook with the current user's balance - function enableBalanceForwarder() external override nonReentrant { - address user = _msgSender(); - uint256 userBalance = this.balanceOf(user); - - _enableBalanceForwarder(user, userBalance); - } + function enableBalanceForwarder() external override use(MODULE_REWARDS) {} /// @notice Disables balance forwarding for the sender /// @dev Should call the IBalanceTracker hook with the account's balance of 0 @@ -304,9 +287,9 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea } /// @notice update accrued interest - // function updateInterestAccrued() external returns (AggregationVaultSavingRateStorage memory) { - // return _updateInterestAccrued(); - // } + function updateInterestAccrued() external { + return _updateInterestAccrued(); + } /// @notice gulp positive harvest yield function gulp() external nonReentrant { @@ -631,7 +614,7 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea $.locked = REENTRANCYLOCK__UNLOCKED; } - function _msgSender() internal view override (ContextUpgradeable, Base) returns (address) { - return Base._msgSender(); + function _msgSender() internal view override (ContextUpgradeable, Shared) returns (address) { + return Shared._msgSender(); } } diff --git a/src/FourSixTwoSixAggFactory.sol b/src/FourSixTwoSixAggFactory.sol index 34e67bee..ef8f8f45 100644 --- a/src/FourSixTwoSixAggFactory.sol +++ b/src/FourSixTwoSixAggFactory.sol @@ -2,19 +2,23 @@ pragma solidity ^0.8.0; import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; -import {Rewards} from "./Rewards.sol"; -import {Hooks} from "./Hooks.sol"; +// core modules +import {Rewards} from "./modules/Rewards.sol"; +import {Hooks} from "./modules/Hooks.sol"; +import {Fee} from "./modules/Fee.sol"; +// core peripheries import {WithdrawalQueue} from "./WithdrawalQueue.sol"; import {FourSixTwoSixAgg} from "./FourSixTwoSixAgg.sol"; contract FourSixTwoSixAggFactory { - // core dependencies + /// core dependencies address public immutable evc; address public immutable balanceTracker; - // core modules + /// core modules address public immutable rewardsImpl; address public immutable hooksImpl; - // peripheries + address public immutable feeModuleImpl; + /// peripheries /// @dev Rebalancer periphery, one instance can serve different aggregation vaults address public immutable rebalancer; /// @dev Withdrawal queue perihperhy, need to be deployed per aggregation vault @@ -25,6 +29,7 @@ contract FourSixTwoSixAggFactory { address _balanceTracker, address _rewardsImpl, address _hooksImpl, + address _feeModuleImpl, address _rebalancer, address _withdrawalQueueImpl ) { @@ -32,6 +37,7 @@ contract FourSixTwoSixAggFactory { balanceTracker = _balanceTracker; rewardsImpl = _rewardsImpl; hooksImpl = _hooksImpl; + feeModuleImpl = _feeModuleImpl; rebalancer = _rebalancer; withdrawalQueueImpl = _withdrawalQueueImpl; @@ -46,13 +52,14 @@ contract FourSixTwoSixAggFactory { ) external returns (address) { // cloning core modules address rewardsImplAddr = Clones.clone(rewardsImpl); - Hooks hooks = Hooks(Clones.clone(hooksImpl)); + address hooks = Clones.clone(hooksImpl); + address feeModule = Clones.clone(feeModuleImpl); // cloning peripheries WithdrawalQueue withdrawalQueue = WithdrawalQueue(Clones.clone(withdrawalQueueImpl)); // deploy new aggregation vault - FourSixTwoSixAgg fourSixTwoSixAgg = new FourSixTwoSixAgg(rewardsImplAddr, address(hooks)); + FourSixTwoSixAgg fourSixTwoSixAgg = new FourSixTwoSixAgg(rewardsImplAddr, hooks, feeModule); withdrawalQueue.init(msg.sender, address(fourSixTwoSixAgg)); fourSixTwoSixAgg.init( diff --git a/src/Hooks.sol b/src/Hooks.sol deleted file mode 100644 index bd03fc55..00000000 --- a/src/Hooks.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Base} from "./Base.sol"; -import {StorageLib, HooksStorage} from "./lib/StorageLib.sol"; -import {HooksLib} from "./lib/HooksLib.sol"; -import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; - -abstract contract HooksModule is Base { - using HooksLib for uint32; - - error InvalidHooksTarget(); - error NotHooksContract(); - error InvalidHookedFns(); - error EmptyError(); - - uint32 public constant DEPOSIT = 1 << 0; - uint32 public constant WITHDRAW = 1 << 1; - uint32 public constant ADD_STRATEGY = 1 << 2; - uint32 public constant REMOVE_STRATEGY = 1 << 3; - uint32 constant ACTIONS_COUNTER = 1 << 4; - - uint256 constant HOOKS_MASK = 0x00000000000000000000000000000000000000000000000000000000FFFFFFFF; - - event SetHooksConfig(address indexed hooksTarget, uint32 hookedFns); - - /// @notice Set hooks contract and hooked functions. - /// @param _hooksTarget Hooks contract. - /// @param _hookedFns Hooked functions. - function setHooksConfig(address _hooksTarget, uint32 _hookedFns) external virtual nonReentrant { - if (_hooksTarget != address(0) && IHookTarget(_hooksTarget).isHookTarget() != IHookTarget.isHookTarget.selector) - { - revert NotHooksContract(); - } - if (_hookedFns != 0 && _hooksTarget == address(0)) { - revert InvalidHooksTarget(); - } - if (_hookedFns >= ACTIONS_COUNTER) revert InvalidHookedFns(); - - HooksStorage storage $ = StorageLib._getHooksStorage(); - $.hooksConfig = (uint256(uint160(_hooksTarget)) << 32) | uint256(_hookedFns); - - emit SetHooksConfig(_hooksTarget, _hookedFns); - } - - /// @notice Get the hooks contract and the hooked functions. - /// @return address Hooks contract. - /// @return uint32 Hooked functions. - function getHooksConfig() external view returns (address, uint32) { - HooksStorage storage $ = StorageLib._getHooksStorage(); - - return _getHooksConfig($.hooksConfig); - } - - /// @notice Checks whether a hook has been installed for the function and if so, invokes the hook target. - /// @param _fn Function to call the hook for. - /// @param _caller Caller's address. - function _callHooksTarget(uint32 _fn, address _caller) internal { - HooksStorage storage $ = StorageLib._getHooksStorage(); - - (address target, uint32 hookedFns) = _getHooksConfig($.hooksConfig); - - if (hookedFns.isNotSet(_fn)) return; - - (bool success, bytes memory data) = target.call(abi.encodePacked(msg.data, _caller)); - - if (!success) _revertBytes(data); - } - - /// @notice Get the hooks contract and the hooked functions. - /// @return address Hooks contract. - /// @return uint32 Hooked functions. - function _getHooksConfig(uint256 _hooksConfig) internal pure returns (address, uint32) { - return (address(uint160(_hooksConfig >> 32)), uint32(_hooksConfig & HOOKS_MASK)); - } - - /// @dev Revert with call error or EmptyError - /// @param _errorMsg call revert message - function _revertBytes(bytes memory _errorMsg) private pure { - if (_errorMsg.length > 0) { - assembly { - revert(add(32, _errorMsg), mload(_errorMsg)) - } - } - - revert EmptyError(); - } -} - -contract Hooks is HooksModule {} diff --git a/src/Rewards.sol b/src/Rewards.sol deleted file mode 100644 index f6a029c1..00000000 --- a/src/Rewards.sol +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Base} from "./Base.sol"; -import {StorageLib, BalanceForwarderStorage, AggregationVaultStorage} from "./lib/StorageLib.sol"; -import {IBalanceForwarder} from "./interface/IBalanceForwarder.sol"; -import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; -import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; -import {ErrorsLib} from "./lib/ErrorsLib.sol"; -import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; - -/// @title BalanceForwarder contract -/// @custom:security-contact security@euler.xyz -/// @author Euler Labs (https://www.eulerlabs.com/) -/// @notice A generic contract to integrate with https://github.com/euler-xyz/reward-streams -abstract contract RewardsModule is IBalanceForwarder, Base { - using StorageLib for *; - - event OptInStrategyRewards(address indexed strategy); - event OptOutStrategyRewards(address indexed strategy); - event EnableBalanceForwarder(address indexed _user); - event DisableBalanceForwarder(address indexed _user); - - /// @notice Opt in to strategy rewards - /// @param _strategy Strategy address - function optInStrategyRewards(address _strategy) external virtual nonReentrant { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - if (!$.strategies[_strategy].active) revert ErrorsLib.InactiveStrategy(); - - IBalanceForwarder(_strategy).enableBalanceForwarder(); - - emit OptInStrategyRewards(_strategy); - } - - /// @notice Opt out of strategy rewards - /// @param _strategy Strategy address - function optOutStrategyRewards(address _strategy) external virtual nonReentrant { - IBalanceForwarder(_strategy).disableBalanceForwarder(); - - emit OptOutStrategyRewards(_strategy); - } - - /// @notice Claim a specific strategy rewards - /// @param _strategy Strategy address. - /// @param _reward The address of the reward token. - /// @param _recipient The address to receive the claimed reward tokens. - /// @param _forfeitRecentReward Whether to forfeit the recent rewards and not update the accumulator. - function claimStrategyReward(address _strategy, address _reward, address _recipient, bool _forfeitRecentReward) - external - virtual - nonReentrant - { - address rewardStreams = IBalanceForwarder(_strategy).balanceTrackerAddress(); - - IRewardStreams(rewardStreams).claimReward(_strategy, _reward, _recipient, _forfeitRecentReward); - } - - /// @notice Enables balance forwarding for the authenticated account - /// @dev Only the authenticated account can enable balance forwarding for itself - /// @dev Should call the IBalanceTracker hook with the current account's balance - function enableBalanceForwarder() external virtual { - return; - } - - /// @notice Disables balance forwarding for the authenticated account - /// @dev Only the authenticated account can disable balance forwarding for itself - /// @dev Should call the IBalanceTracker hook with the account's balance of 0 - function disableBalanceForwarder() external virtual nonReentrant { - _disableBalanceForwarder(_msgSender()); - } - - /// @notice Retrieve the address of rewards contract, tracking changes in account's balances - /// @return The balance tracker address - function balanceTrackerAddress() external view returns (address) { - BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); - - return address($.balanceTracker); - } - - /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract - /// @param _account Address to query - /// @return True if balance forwarder is enabled - function balanceForwarderEnabled(address _account) external view returns (bool) { - return _balanceForwarderEnabled(_account); - } - - function _enableBalanceForwarder(address _sender, uint256 _senderBalance) internal { - BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); - IBalanceTracker balanceTrackerCached = IBalanceTracker($.balanceTracker); - - if (address(balanceTrackerCached) == address(0)) revert ErrorsLib.NotSupported(); - if ($.isBalanceForwarderEnabled[_sender]) revert ErrorsLib.AlreadyEnabled(); - - $.isBalanceForwarderEnabled[_sender] = true; - balanceTrackerCached.balanceTrackerHook(_sender, _senderBalance, false); - - emit EnableBalanceForwarder(_sender); - } - - /// @notice Disables balance forwarding for the authenticated account - /// @dev Only the authenticated account can disable balance forwarding for itself - /// @dev Should call the IBalanceTracker hook with the account's balance of 0 - function _disableBalanceForwarder(address _sender) internal { - BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); - IBalanceTracker balanceTrackerCached = IBalanceTracker($.balanceTracker); - - if (address(balanceTrackerCached) == address(0)) revert ErrorsLib.NotSupported(); - if (!$.isBalanceForwarderEnabled[_sender]) revert ErrorsLib.AlreadyDisabled(); - - $.isBalanceForwarderEnabled[_sender] = false; - balanceTrackerCached.balanceTrackerHook(_sender, 0, false); - - emit DisableBalanceForwarder(_sender); - } - - function _setBalanceTracker(address _balancerTracker) internal { - BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); - - $.balanceTracker = _balancerTracker; - } - - /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract - /// @param _account Address to query - /// @return True if balance forwarder is enabled - function _balanceForwarderEnabled(address _account) internal view returns (bool) { - BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); - - return $.isBalanceForwarderEnabled[_account]; - } - - /// @notice Retrieve the address of rewards contract, tracking changes in account's balances - /// @return The balance tracker address - function _balanceTrackerAddress() internal view returns (address) { - BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); - - return address($.balanceTracker); - } -} - -contract Rewards is RewardsModule {} diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 6ea444b8..6de1dd67 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -5,8 +5,9 @@ import "evk/test/unit/evault/EVaultTestBase.t.sol"; import {FourSixTwoSixAgg, Strategy} from "../../src/FourSixTwoSixAgg.sol"; import {Rebalancer} from "../../src/Rebalancer.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; -import {Hooks, HooksModule} from "../../src/Hooks.sol"; -import {Rewards} from "../../src/Rewards.sol"; +import {Hooks, HooksModule} from "../../src/modules/Hooks.sol"; +import {Rewards} from "../../src/modules/Rewards.sol"; +import {Fee} from "../../src/modules/Fee.sol"; import {FourSixTwoSixAggFactory} from "../../src/FourSixTwoSixAggFactory.sol"; import {WithdrawalQueue} from "../../src/WithdrawalQueue.sol"; import {IWithdrawalQueue} from "../../src/interface/IWithdrawalQueue.sol"; @@ -23,6 +24,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { // core modules Rewards rewardsImpl; Hooks hooksImpl; + Fee feeModuleImpl; // peripheries Rebalancer rebalancer; WithdrawalQueue withdrawalQueueImpl; @@ -42,6 +44,8 @@ contract FourSixTwoSixAggBase is EVaultTestBase { vm.startPrank(deployer); rewardsImpl = new Rewards(); hooksImpl = new Hooks(); + feeModuleImpl = new Fee(); + rebalancer = new Rebalancer(); withdrawalQueueImpl = new WithdrawalQueue(); fourSixTwoSixAggFactory = new FourSixTwoSixAggFactory( @@ -49,6 +53,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { address(0), address(rewardsImpl), address(hooksImpl), + address(feeModuleImpl), address(rebalancer), address(withdrawalQueueImpl) ); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 2120fb80..b02fca97 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -30,6 +30,7 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { trackingReward, address(rewardsImpl), address(hooksImpl), + address(feeModuleImpl), address(rebalancer), address(withdrawalQueueImpl) ); From 18756aa4d14218744756a129a61afcfff0ed6272 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 18 Jun 2024 12:09:53 +0300 Subject: [PATCH 176/316] working version --- src/FourSixTwoSixAgg.sol | 41 +++++++++++++++------------------ src/FourSixTwoSixAggFactory.sol | 24 ++++++++++--------- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index f109edf8..ee506d51 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -76,37 +76,32 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea // /// @param _initialCashAllocationPoints Initial points to be allocated to the cash reserve // /// @param _initialStrategies An array of initial strategies addresses // /// @param _initialStrategiesAllocationPoints An array of initial strategies allocation points - function init( - address _evc, - address _balanceTracker, - address _withdrawalQueue, - address _rebalancer, - address _owner, - address _asset, - string memory _name, - string memory _symbol, - uint256 _initialCashAllocationPoints - ) external initializer { - __ERC4626_init_unchained(IERC20(_asset)); - __ERC20_init_unchained(_name, _symbol); + + function init(InitParams calldata _initParams) external initializer { + __ERC4626_init_unchained(IERC20(_initParams.asset)); + __ERC20_init_unchained(_initParams.name, _initParams.symbol); _lock(); - if (_initialCashAllocationPoints == 0) revert ErrorsLib.InitialAllocationPointsZero(); + if (_initParams.initialCashAllocationPoints == 0) revert ErrorsLib.InitialAllocationPointsZero(); AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - $.withdrawalQueue = _withdrawalQueue; - $.strategies[address(0)] = - Strategy({allocated: 0, allocationPoints: _initialCashAllocationPoints.toUint120(), active: true, cap: 0}); - $.totalAllocationPoints = _initialCashAllocationPoints; - $.evc = _evc; - - _setBalanceTracker(_balanceTracker); + $.withdrawalQueue = _initParams.withdrawalQueuePeriphery; + $.strategies[address(0)] = Strategy({ + allocated: 0, + allocationPoints: _initParams.initialCashAllocationPoints.toUint120(), + active: true, + cap: 0 + }); + $.totalAllocationPoints = _initParams.initialCashAllocationPoints; + $.evc = _initParams.evc; + + _setBalanceTracker(_initParams.balanceTracker); // Setup DEFAULT_ADMIN - _grantRole(DEFAULT_ADMIN_ROLE, _owner); + _grantRole(DEFAULT_ADMIN_ROLE, _initParams.aggregationVaultOwner); // By default, the Rebalancer contract is assigned the REBALANCER role - _grantRole(REBALANCER, _rebalancer); + _grantRole(REBALANCER, _initParams.rebalancerPerihpery); // Setup role admins _setRoleAdmin(STRATEGY_MANAGER, STRATEGY_MANAGER_ADMIN); diff --git a/src/FourSixTwoSixAggFactory.sol b/src/FourSixTwoSixAggFactory.sol index ef8f8f45..5e9a6ac5 100644 --- a/src/FourSixTwoSixAggFactory.sol +++ b/src/FourSixTwoSixAggFactory.sol @@ -61,18 +61,20 @@ contract FourSixTwoSixAggFactory { // deploy new aggregation vault FourSixTwoSixAgg fourSixTwoSixAgg = new FourSixTwoSixAgg(rewardsImplAddr, hooks, feeModule); + FourSixTwoSixAgg.InitParams memory aggregationVaultInitParams = FourSixTwoSixAgg.InitParams({ + evc: evc, + balanceTracker: balanceTracker, + withdrawalQueuePeriphery: address(withdrawalQueue), + rebalancerPerihpery: rebalancer, + aggregationVaultOwner: msg.sender, + asset: _asset, + name: _name, + symbol: _symbol, + initialCashAllocationPoints: _initialCashAllocationPoints + }); + withdrawalQueue.init(msg.sender, address(fourSixTwoSixAgg)); - fourSixTwoSixAgg.init( - evc, - balanceTracker, - address(withdrawalQueue), - rebalancer, - msg.sender, - _asset, - _name, - _symbol, - _initialCashAllocationPoints - ); + fourSixTwoSixAgg.init(aggregationVaultInitParams); return address(fourSixTwoSixAgg); } From 328270908288d70116e692224ba91ba7b3da4924 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 18 Jun 2024 12:40:42 +0300 Subject: [PATCH 177/316] bytecode under limit --- src/Dispatch.sol | 6 +- src/FourSixTwoSixAgg.sol | 86 +++------------ src/FourSixTwoSixAggFactory.sol | 41 +++---- src/Shared.sol | 96 +++++++++++++++++ src/lib/ErrorsLib.sol | 4 + src/modules/AllocationPoints.sol | 109 +++++++++++++++++++ src/modules/Fee.sol | 51 +++++++++ src/modules/Hooks.sol | 29 +++++ src/modules/Rewards.sol | 144 +++++++++++++++++++++++++ test/common/FourSixTwoSixAggBase.t.sol | 4 + test/e2e/BalanceForwarderE2ETest.t.sol | 1 + test/e2e/HooksE2ETest.t.sol | 8 +- 12 files changed, 485 insertions(+), 94 deletions(-) create mode 100644 src/Shared.sol create mode 100644 src/modules/AllocationPoints.sol create mode 100644 src/modules/Fee.sol create mode 100644 src/modules/Hooks.sol create mode 100644 src/modules/Rewards.sol diff --git a/src/Dispatch.sol b/src/Dispatch.sol index 60e96a49..16a75f16 100644 --- a/src/Dispatch.sol +++ b/src/Dispatch.sol @@ -12,11 +12,15 @@ abstract contract Dispatch is RewardsModule, HooksModule { address public immutable MODULE_REWARDS; address public immutable MODULE_HOOKS; address public immutable MODULE_FEE; + address public immutable MODULE_ALLOCATION_POINTS; - constructor(address _rewardsModule, address _hooksModule, address _feeModule) Shared() { + constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) + Shared() + { MODULE_REWARDS = _rewardsModule; MODULE_HOOKS = _hooksModule; MODULE_FEE = _feeModule; + MODULE_ALLOCATION_POINTS = _allocationPointsModule; } // Modifier proxies the function call to a module and low-level returns the result diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index ee506d51..3d518e81 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -54,8 +54,8 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea event SetStrategyCap(address indexed strategy, uint256 cap); event Rebalance(address indexed strategy, uint256 _amountToRebalance, bool _isDeposit); - constructor(address _rewardsModule, address _hooksModule, address _feeModule) - Dispatch(_rewardsModule, _hooksModule, _feeModule) + constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) + Dispatch(_rewardsModule, _hooksModule, _feeModule, _allocationPointsModule) {} struct InitParams { @@ -195,91 +195,35 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea /// @param _newPoints new strategy's points function adjustAllocationPoints(address _strategy, uint256 _newPoints) external - nonReentrant + use(MODULE_ALLOCATION_POINTS) onlyRole(STRATEGY_MANAGER) - { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - Strategy memory strategyDataCache = $.strategies[_strategy]; - - if (!strategyDataCache.active) { - revert ErrorsLib.InactiveStrategy(); - } - - $.strategies[_strategy].allocationPoints = _newPoints.toUint120(); - $.totalAllocationPoints = $.totalAllocationPoints + _newPoints - strategyDataCache.allocationPoints; - - emit AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); - } + {} /// @notice Set cap on strategy allocated amount. /// @dev By default, cap is set to 0, not activated. /// @param _strategy Strategy address. /// @param _cap Cap amount - function setStrategyCap(address _strategy, uint256 _cap) external nonReentrant onlyRole(STRATEGY_MANAGER) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - if (!$.strategies[_strategy].active) { - revert ErrorsLib.InactiveStrategy(); - } - - $.strategies[_strategy].cap = _cap.toUint120(); - - emit SetStrategyCap(_strategy, _cap); - } + function setStrategyCap(address _strategy, uint256 _cap) + external + use(MODULE_ALLOCATION_POINTS) + onlyRole(STRATEGY_MANAGER) + {} /// @notice Add new strategy with it's allocation points. /// @dev Can only be called by an address that have STRATEGY_ADDER. /// @param _strategy Address of the strategy /// @param _allocationPoints Strategy's allocation points - function addStrategy(address _strategy, uint256 _allocationPoints) external nonReentrant onlyRole(STRATEGY_ADDER) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - if ($.strategies[_strategy].active) { - revert ErrorsLib.StrategyAlreadyExist(); - } - - if (IERC4626(_strategy).asset() != asset()) { - revert ErrorsLib.InvalidStrategyAsset(); - } - - _callHooksTarget(ADD_STRATEGY, _msgSender()); - - $.strategies[_strategy] = - Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); - - $.totalAllocationPoints += _allocationPoints; - IWithdrawalQueue($.withdrawalQueue).addStrategyToWithdrawalQueue(_strategy); - - emit AddStrategy(_strategy, _allocationPoints); - } + function addStrategy(address _strategy, uint256 _allocationPoints) + external + use(MODULE_ALLOCATION_POINTS) + onlyRole(STRATEGY_ADDER) + {} /// @notice Remove strategy and set its allocation points to zero. /// @dev This function does not pull funds, `harvest()` needs to be called to withdraw /// @dev Can only be called by an address that have the STRATEGY_REMOVER /// @param _strategy Address of the strategy - function removeStrategy(address _strategy) external nonReentrant onlyRole(STRATEGY_REMOVER) { - if (_strategy == address(0)) revert ErrorsLib.CanNotRemoveCashReserve(); - - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - Strategy storage strategyStorage = $.strategies[_strategy]; - - if (!strategyStorage.active) { - revert ErrorsLib.AlreadyRemoved(); - } - - _callHooksTarget(REMOVE_STRATEGY, _msgSender()); - - $.totalAllocationPoints -= strategyStorage.allocationPoints; - strategyStorage.active = false; - strategyStorage.allocationPoints = 0; - - // remove from withdrawalQueue - IWithdrawalQueue($.withdrawalQueue).removeStrategyFromWithdrawalQueue(_strategy); - - emit RemoveStrategy(_strategy); - } + function removeStrategy(address _strategy) external use(MODULE_ALLOCATION_POINTS) onlyRole(STRATEGY_REMOVER) {} /// @notice update accrued interest function updateInterestAccrued() external { diff --git a/src/FourSixTwoSixAggFactory.sol b/src/FourSixTwoSixAggFactory.sol index 5e9a6ac5..00465d44 100644 --- a/src/FourSixTwoSixAggFactory.sol +++ b/src/FourSixTwoSixAggFactory.sol @@ -15,32 +15,35 @@ contract FourSixTwoSixAggFactory { address public immutable evc; address public immutable balanceTracker; /// core modules - address public immutable rewardsImpl; - address public immutable hooksImpl; + address public immutable rewardsModuleImpl; + address public immutable hooksModuleImpl; address public immutable feeModuleImpl; + address public immutable allocationpointsModuleImpl; /// peripheries /// @dev Rebalancer periphery, one instance can serve different aggregation vaults - address public immutable rebalancer; + address public immutable rebalancerAddr; /// @dev Withdrawal queue perihperhy, need to be deployed per aggregation vault - address public immutable withdrawalQueueImpl; + address public immutable withdrawalQueueAddr; constructor( address _evc, address _balanceTracker, - address _rewardsImpl, - address _hooksImpl, + address _rewardsModuleImpl, + address _hooksModuleImpl, address _feeModuleImpl, - address _rebalancer, - address _withdrawalQueueImpl + address _allocationpointsModuleImpl, + address _rebalancerAddr, + address _withdrawalQueueAddr ) { evc = _evc; balanceTracker = _balanceTracker; - rewardsImpl = _rewardsImpl; - hooksImpl = _hooksImpl; + rewardsModuleImpl = _rewardsModuleImpl; + hooksModuleImpl = _hooksModuleImpl; feeModuleImpl = _feeModuleImpl; + allocationpointsModuleImpl = _allocationpointsModuleImpl; - rebalancer = _rebalancer; - withdrawalQueueImpl = _withdrawalQueueImpl; + rebalancerAddr = _rebalancerAddr; + withdrawalQueueAddr = _withdrawalQueueAddr; } // TODO: decrease bytecode size, use clones or something @@ -51,21 +54,23 @@ contract FourSixTwoSixAggFactory { uint256 _initialCashAllocationPoints ) external returns (address) { // cloning core modules - address rewardsImplAddr = Clones.clone(rewardsImpl); - address hooks = Clones.clone(hooksImpl); - address feeModule = Clones.clone(feeModuleImpl); + address rewardsModuleAddr = Clones.clone(rewardsModuleImpl); + address hooksModuleAddr = Clones.clone(hooksModuleImpl); + address feeModuleAddr = Clones.clone(feeModuleImpl); + address allocationpointsModuleAddr = Clones.clone(allocationpointsModuleImpl); // cloning peripheries - WithdrawalQueue withdrawalQueue = WithdrawalQueue(Clones.clone(withdrawalQueueImpl)); + WithdrawalQueue withdrawalQueue = WithdrawalQueue(Clones.clone(withdrawalQueueAddr)); // deploy new aggregation vault - FourSixTwoSixAgg fourSixTwoSixAgg = new FourSixTwoSixAgg(rewardsImplAddr, hooks, feeModule); + FourSixTwoSixAgg fourSixTwoSixAgg = + new FourSixTwoSixAgg(rewardsModuleAddr, hooksModuleAddr, feeModuleAddr, allocationpointsModuleAddr); FourSixTwoSixAgg.InitParams memory aggregationVaultInitParams = FourSixTwoSixAgg.InitParams({ evc: evc, balanceTracker: balanceTracker, withdrawalQueuePeriphery: address(withdrawalQueue), - rebalancerPerihpery: rebalancer, + rebalancerPerihpery: rebalancerAddr, aggregationVaultOwner: msg.sender, asset: _asset, name: _name, diff --git a/src/Shared.sol b/src/Shared.sol new file mode 100644 index 00000000..d9e29d19 --- /dev/null +++ b/src/Shared.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {StorageLib, AggregationVaultStorage, HooksStorage} from "./lib/StorageLib.sol"; +import {ErrorsLib} from "./lib/ErrorsLib.sol"; +import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; +import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; +import {HooksLib} from "./lib/HooksLib.sol"; + +contract Shared { + using HooksLib for uint32; + + uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; + uint8 internal constant REENTRANCYLOCK__LOCKED = 2; + + uint32 public constant DEPOSIT = 1 << 0; + uint32 public constant WITHDRAW = 1 << 1; + uint32 public constant ADD_STRATEGY = 1 << 2; + uint32 public constant REMOVE_STRATEGY = 1 << 3; + + uint32 constant ACTIONS_COUNTER = 1 << 4; + uint256 constant HOOKS_MASK = 0x00000000000000000000000000000000000000000000000000000000FFFFFFFF; + + event SetHooksConfig(address indexed hooksTarget, uint32 hookedFns); + + modifier nonReentrant() { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if ($.locked == REENTRANCYLOCK__LOCKED) revert ErrorsLib.Reentrancy(); + + $.locked = REENTRANCYLOCK__LOCKED; + _; + $.locked = REENTRANCYLOCK__UNLOCKED; + } + + function _msgSender() internal view virtual returns (address) { + address sender = msg.sender; + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if (sender == address($.evc)) { + (sender,) = IEVC($.evc).getCurrentOnBehalfOfAccount(address(0)); + } + + return sender; + } + + function _setHooksConfig(address _hooksTarget, uint32 _hookedFns) internal { + if (_hooksTarget != address(0) && IHookTarget(_hooksTarget).isHookTarget() != IHookTarget.isHookTarget.selector) + { + revert ErrorsLib.NotHooksContract(); + } + if (_hookedFns != 0 && _hooksTarget == address(0)) { + revert ErrorsLib.InvalidHooksTarget(); + } + if (_hookedFns >= ACTIONS_COUNTER) revert ErrorsLib.InvalidHookedFns(); + + HooksStorage storage $ = StorageLib._getHooksStorage(); + $.hooksConfig = (uint256(uint160(_hooksTarget)) << 32) | uint256(_hookedFns); + + emit SetHooksConfig(_hooksTarget, _hookedFns); + } + + /// @notice Checks whether a hook has been installed for the function and if so, invokes the hook target. + /// @param _fn Function to call the hook for. + /// @param _caller Caller's address. + function _callHooksTarget(uint32 _fn, address _caller) internal { + HooksStorage storage $ = StorageLib._getHooksStorage(); + + (address target, uint32 hookedFns) = _getHooksConfig($.hooksConfig); + + if (hookedFns.isNotSet(_fn)) return; + + (bool success, bytes memory data) = target.call(abi.encodePacked(msg.data, _caller)); + + if (!success) _revertBytes(data); + } + + /// @notice Get the hooks contract and the hooked functions. + /// @return address Hooks contract. + /// @return uint32 Hooked functions. + function _getHooksConfig(uint256 _hooksConfig) internal pure returns (address, uint32) { + return (address(uint160(_hooksConfig >> 32)), uint32(_hooksConfig & HOOKS_MASK)); + } + + /// @dev Revert with call error or EmptyError + /// @param _errorMsg call revert message + function _revertBytes(bytes memory _errorMsg) private pure { + if (_errorMsg.length > 0) { + assembly { + revert(add(32, _errorMsg), mload(_errorMsg)) + } + } + + revert ErrorsLib.EmptyError(); + } +} diff --git a/src/lib/ErrorsLib.sol b/src/lib/ErrorsLib.sol index 3b57e3b8..d7599b39 100644 --- a/src/lib/ErrorsLib.sol +++ b/src/lib/ErrorsLib.sol @@ -19,4 +19,8 @@ library ErrorsLib { error NotSupported(); error AlreadyEnabled(); error AlreadyDisabled(); + error InvalidHooksTarget(); + error NotHooksContract(); + error InvalidHookedFns(); + error EmptyError(); } diff --git a/src/modules/AllocationPoints.sol b/src/modules/AllocationPoints.sol new file mode 100644 index 00000000..6029741e --- /dev/null +++ b/src/modules/AllocationPoints.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Shared} from "../Shared.sol"; +// internal dep +import {StorageLib, AggregationVaultStorage, Strategy} from "../lib/StorageLib.sol"; +import {ErrorsLib} from "../lib/ErrorsLib.sol"; +import {IERC4626} from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import {IWithdrawalQueue} from "../interface/IWithdrawalQueue.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +abstract contract AllocationPointsModule is Shared { + using SafeCast for uint256; + + event AdjustAllocationPoints(address indexed strategy, uint256 oldPoints, uint256 newPoints); + event AddStrategy(address indexed strategy, uint256 allocationPoints); + event RemoveStrategy(address indexed _strategy); + event SetStrategyCap(address indexed strategy, uint256 cap); + + /// @notice Adjust a certain strategy's allocation points. + /// @dev Can only be called by an address that have the STRATEGY_MANAGER + /// @param _strategy address of strategy + /// @param _newPoints new strategy's points + function adjustAllocationPoints(address _strategy, uint256 _newPoints) external virtual nonReentrant { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + Strategy memory strategyDataCache = $.strategies[_strategy]; + + if (!strategyDataCache.active) { + revert ErrorsLib.InactiveStrategy(); + } + + $.strategies[_strategy].allocationPoints = _newPoints.toUint120(); + $.totalAllocationPoints = $.totalAllocationPoints + _newPoints - strategyDataCache.allocationPoints; + + emit AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); + } + + /// @notice Set cap on strategy allocated amount. + /// @dev By default, cap is set to 0, not activated. + /// @param _strategy Strategy address. + /// @param _cap Cap amount + function setStrategyCap(address _strategy, uint256 _cap) external virtual nonReentrant { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if (!$.strategies[_strategy].active) { + revert ErrorsLib.InactiveStrategy(); + } + + $.strategies[_strategy].cap = _cap.toUint120(); + + emit SetStrategyCap(_strategy, _cap); + } + + /// @notice Add new strategy with it's allocation points. + /// @dev Can only be called by an address that have STRATEGY_ADDER. + /// @param _strategy Address of the strategy + /// @param _allocationPoints Strategy's allocation points + function addStrategy(address _strategy, uint256 _allocationPoints) external virtual nonReentrant { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if ($.strategies[_strategy].active) { + revert ErrorsLib.StrategyAlreadyExist(); + } + + if (IERC4626(_strategy).asset() != IERC4626(address(this)).asset()) { + revert ErrorsLib.InvalidStrategyAsset(); + } + + _callHooksTarget(ADD_STRATEGY, _msgSender()); + + $.strategies[_strategy] = + Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); + + $.totalAllocationPoints += _allocationPoints; + IWithdrawalQueue($.withdrawalQueue).addStrategyToWithdrawalQueue(_strategy); + + emit AddStrategy(_strategy, _allocationPoints); + } + + /// @notice Remove strategy and set its allocation points to zero. + /// @dev This function does not pull funds, `harvest()` needs to be called to withdraw + /// @dev Can only be called by an address that have the STRATEGY_REMOVER + /// @param _strategy Address of the strategy + function removeStrategy(address _strategy) external virtual nonReentrant { + if (_strategy == address(0)) revert ErrorsLib.CanNotRemoveCashReserve(); + + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + Strategy storage strategyStorage = $.strategies[_strategy]; + + if (!strategyStorage.active) { + revert ErrorsLib.AlreadyRemoved(); + } + + _callHooksTarget(REMOVE_STRATEGY, _msgSender()); + + $.totalAllocationPoints -= strategyStorage.allocationPoints; + strategyStorage.active = false; + strategyStorage.allocationPoints = 0; + + // remove from withdrawalQueue + IWithdrawalQueue($.withdrawalQueue).removeStrategyFromWithdrawalQueue(_strategy); + + emit RemoveStrategy(_strategy); + } +} + +contract AllocationPoints is AllocationPointsModule {} diff --git a/src/modules/Fee.sol b/src/modules/Fee.sol new file mode 100644 index 00000000..6707efa5 --- /dev/null +++ b/src/modules/Fee.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Shared} from "../Shared.sol"; +import {StorageLib, BalanceForwarderStorage, AggregationVaultStorage} from "../lib/StorageLib.sol"; +import {IBalanceForwarder} from "../interface/IBalanceForwarder.sol"; +import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; +import {ErrorsLib} from "../lib/ErrorsLib.sol"; +import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +abstract contract FeeModule is Shared { + using StorageLib for *; + + /// @dev The maximum performanceFee the vault can have is 50% + uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; + + event SetFeeRecipient(address indexed oldRecipient, address indexed newRecipient); + event SetPerformanceFee(uint256 oldFee, uint256 newFee); + + /// @notice Set performance fee recipient address + /// @notice @param _newFeeRecipient Recipient address + function setFeeRecipient(address _newFeeRecipient) external { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + address feeRecipientCached = $.feeRecipient; + + if (_newFeeRecipient == feeRecipientCached) revert ErrorsLib.FeeRecipientAlreadySet(); + + emit SetFeeRecipient(feeRecipientCached, _newFeeRecipient); + + $.feeRecipient = _newFeeRecipient; + } + + /// @notice Set performance fee (1e18 == 100%) + /// @notice @param _newFee Fee rate + function setPerformanceFee(uint256 _newFee) external { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + uint256 performanceFeeCached = $.performanceFee; + + if (_newFee > MAX_PERFORMANCE_FEE) revert ErrorsLib.MaxPerformanceFeeExceeded(); + if ($.feeRecipient == address(0)) revert ErrorsLib.FeeRecipientNotSet(); + if (_newFee == performanceFeeCached) revert ErrorsLib.PerformanceFeeAlreadySet(); + + emit SetPerformanceFee(performanceFeeCached, _newFee); + + $.performanceFee = _newFee; + } +} + +contract Fee is FeeModule {} diff --git a/src/modules/Hooks.sol b/src/modules/Hooks.sol new file mode 100644 index 00000000..8fee680b --- /dev/null +++ b/src/modules/Hooks.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Shared} from "../Shared.sol"; +import {StorageLib, HooksStorage} from "../lib/StorageLib.sol"; +import {HooksLib} from "../lib/HooksLib.sol"; +import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; + +abstract contract HooksModule is Shared { + using HooksLib for uint32; + + /// @notice Set hooks contract and hooked functions. + /// @param _hooksTarget Hooks contract. + /// @param _hookedFns Hooked functions. + function setHooksConfig(address _hooksTarget, uint32 _hookedFns) external virtual nonReentrant { + _setHooksConfig(_hooksTarget, _hookedFns); + } + + /// @notice Get the hooks contract and the hooked functions. + /// @return address Hooks contract. + /// @return uint32 Hooked functions. + function getHooksConfig() external view returns (address, uint32) { + HooksStorage storage $ = StorageLib._getHooksStorage(); + + return _getHooksConfig($.hooksConfig); + } +} + +contract Hooks is HooksModule {} diff --git a/src/modules/Rewards.sol b/src/modules/Rewards.sol new file mode 100644 index 00000000..5afb4a72 --- /dev/null +++ b/src/modules/Rewards.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Shared} from "../Shared.sol"; +import {StorageLib, BalanceForwarderStorage, AggregationVaultStorage} from "../lib/StorageLib.sol"; +import {IBalanceForwarder} from "../interface/IBalanceForwarder.sol"; +import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; +import {ErrorsLib} from "../lib/ErrorsLib.sol"; +import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/// @title BalanceForwarder contract +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice A generic contract to integrate with https://github.com/euler-xyz/reward-streams +abstract contract RewardsModule is IBalanceForwarder, Shared { + using StorageLib for *; + + event OptInStrategyRewards(address indexed strategy); + event OptOutStrategyRewards(address indexed strategy); + event EnableBalanceForwarder(address indexed _user); + event DisableBalanceForwarder(address indexed _user); + + /// @notice Opt in to strategy rewards + /// @param _strategy Strategy address + function optInStrategyRewards(address _strategy) external virtual nonReentrant { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if (!$.strategies[_strategy].active) revert ErrorsLib.InactiveStrategy(); + + IBalanceForwarder(_strategy).enableBalanceForwarder(); + + emit OptInStrategyRewards(_strategy); + } + + /// @notice Opt out of strategy rewards + /// @param _strategy Strategy address + function optOutStrategyRewards(address _strategy) external virtual nonReentrant { + IBalanceForwarder(_strategy).disableBalanceForwarder(); + + emit OptOutStrategyRewards(_strategy); + } + + /// @notice Claim a specific strategy rewards + /// @param _strategy Strategy address. + /// @param _reward The address of the reward token. + /// @param _recipient The address to receive the claimed reward tokens. + /// @param _forfeitRecentReward Whether to forfeit the recent rewards and not update the accumulator. + function claimStrategyReward(address _strategy, address _reward, address _recipient, bool _forfeitRecentReward) + external + virtual + nonReentrant + { + address rewardStreams = IBalanceForwarder(_strategy).balanceTrackerAddress(); + + IRewardStreams(rewardStreams).claimReward(_strategy, _reward, _recipient, _forfeitRecentReward); + } + + /// @notice Enables balance forwarding for the authenticated account + /// @dev Only the authenticated account can enable balance forwarding for itself + /// @dev Should call the IBalanceTracker hook with the current account's balance + function enableBalanceForwarder() external virtual nonReentrant { + address user = _msgSender(); + uint256 userBalance = IERC20(address(this)).balanceOf(user); + + _enableBalanceForwarder(user, userBalance); + } + + /// @notice Disables balance forwarding for the authenticated account + /// @dev Only the authenticated account can disable balance forwarding for itself + /// @dev Should call the IBalanceTracker hook with the account's balance of 0 + function disableBalanceForwarder() external virtual nonReentrant { + _disableBalanceForwarder(_msgSender()); + } + + /// @notice Retrieve the address of rewards contract, tracking changes in account's balances + /// @return The balance tracker address + function balanceTrackerAddress() external view returns (address) { + BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + + return address($.balanceTracker); + } + + /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract + /// @param _account Address to query + /// @return True if balance forwarder is enabled + function balanceForwarderEnabled(address _account) external view returns (bool) { + return _balanceForwarderEnabled(_account); + } + + function _enableBalanceForwarder(address _sender, uint256 _senderBalance) internal { + BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + IBalanceTracker balanceTrackerCached = IBalanceTracker($.balanceTracker); + + if (address(balanceTrackerCached) == address(0)) revert ErrorsLib.NotSupported(); + if ($.isBalanceForwarderEnabled[_sender]) revert ErrorsLib.AlreadyEnabled(); + + $.isBalanceForwarderEnabled[_sender] = true; + balanceTrackerCached.balanceTrackerHook(_sender, _senderBalance, false); + + emit EnableBalanceForwarder(_sender); + } + + /// @notice Disables balance forwarding for the authenticated account + /// @dev Only the authenticated account can disable balance forwarding for itself + /// @dev Should call the IBalanceTracker hook with the account's balance of 0 + function _disableBalanceForwarder(address _sender) internal { + BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + IBalanceTracker balanceTrackerCached = IBalanceTracker($.balanceTracker); + + if (address(balanceTrackerCached) == address(0)) revert ErrorsLib.NotSupported(); + if (!$.isBalanceForwarderEnabled[_sender]) revert ErrorsLib.AlreadyDisabled(); + + $.isBalanceForwarderEnabled[_sender] = false; + balanceTrackerCached.balanceTrackerHook(_sender, 0, false); + + emit DisableBalanceForwarder(_sender); + } + + function _setBalanceTracker(address _balancerTracker) internal { + BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + + $.balanceTracker = _balancerTracker; + } + + /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract + /// @param _account Address to query + /// @return True if balance forwarder is enabled + function _balanceForwarderEnabled(address _account) internal view returns (bool) { + BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + + return $.isBalanceForwarderEnabled[_account]; + } + + /// @notice Retrieve the address of rewards contract, tracking changes in account's balances + /// @return The balance tracker address + function _balanceTrackerAddress() internal view returns (address) { + BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + + return address($.balanceTracker); + } +} + +contract Rewards is RewardsModule {} diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 6de1dd67..0268dcd7 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -12,6 +12,7 @@ import {FourSixTwoSixAggFactory} from "../../src/FourSixTwoSixAggFactory.sol"; import {WithdrawalQueue} from "../../src/WithdrawalQueue.sol"; import {IWithdrawalQueue} from "../../src/interface/IWithdrawalQueue.sol"; import {ErrorsLib} from "../../src/lib/ErrorsLib.sol"; +import {AllocationPoints} from "../../src/modules/AllocationPoints.sol"; contract FourSixTwoSixAggBase is EVaultTestBase { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; @@ -25,6 +26,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { Rewards rewardsImpl; Hooks hooksImpl; Fee feeModuleImpl; + AllocationPoints allocationPointsModuleImpl; // peripheries Rebalancer rebalancer; WithdrawalQueue withdrawalQueueImpl; @@ -45,6 +47,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { rewardsImpl = new Rewards(); hooksImpl = new Hooks(); feeModuleImpl = new Fee(); + allocationPointsModuleImpl = new AllocationPoints(); rebalancer = new Rebalancer(); withdrawalQueueImpl = new WithdrawalQueue(); @@ -54,6 +57,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { address(rewardsImpl), address(hooksImpl), address(feeModuleImpl), + address(allocationPointsModuleImpl), address(rebalancer), address(withdrawalQueueImpl) ); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index b02fca97..9e45b94b 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -31,6 +31,7 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { address(rewardsImpl), address(hooksImpl), address(feeModuleImpl), + address(allocationPointsModuleImpl), address(rebalancer), address(withdrawalQueueImpl) ); diff --git a/test/e2e/HooksE2ETest.t.sol b/test/e2e/HooksE2ETest.t.sol index a0e966c0..a7570b48 100644 --- a/test/e2e/HooksE2ETest.t.sol +++ b/test/e2e/HooksE2ETest.t.sol @@ -10,7 +10,7 @@ import { IRMTestDefault, TestERC20, IHookTarget, - HooksModule + ErrorsLib } from "../common/FourSixTwoSixAggBase.t.sol"; contract HooksE2ETest is FourSixTwoSixAggBase { @@ -45,7 +45,7 @@ contract HooksE2ETest is FourSixTwoSixAggBase { | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); vm.startPrank(manager); - vm.expectRevert(HooksModule.InvalidHooksTarget.selector); + vm.expectRevert(ErrorsLib.InvalidHooksTarget.selector); fourSixTwoSixAgg.setHooksConfig(address(0), expectedHookedFns); vm.stopPrank(); } @@ -56,7 +56,7 @@ contract HooksE2ETest is FourSixTwoSixAggBase { vm.startPrank(manager); address hooksContract = address(new NotHooksContract()); - vm.expectRevert(HooksModule.NotHooksContract.selector); + vm.expectRevert(ErrorsLib.NotHooksContract.selector); fourSixTwoSixAgg.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); } @@ -65,7 +65,7 @@ contract HooksE2ETest is FourSixTwoSixAggBase { uint32 expectedHookedFns = 1 << 5; vm.startPrank(manager); address hooksContract = address(new HooksContract()); - vm.expectRevert(HooksModule.InvalidHookedFns.selector); + vm.expectRevert(ErrorsLib.InvalidHookedFns.selector); fourSixTwoSixAgg.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); } From c6f29f26358e4c766b63467a122047022c1a4c8f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 18 Jun 2024 17:30:24 +0300 Subject: [PATCH 178/316] clean --- foundry.toml | 3 + src/Dispatch.sol | 2 - src/FourSixTwoSixAgg.sol | 205 ++++++++++++++----------------- src/Shared.sol | 10 +- src/lib/EventsLib.sol | 29 +++++ src/lib/StorageLib.sol | 29 +---- src/modules/AllocationPoints.sol | 24 ++-- src/modules/Fee.sol | 19 ++- src/modules/Hooks.sol | 12 +- src/modules/Rewards.sol | 38 +++--- 10 files changed, 183 insertions(+), 188 deletions(-) create mode 100644 src/lib/EventsLib.sol diff --git a/foundry.toml b/foundry.toml index 518361a2..c1df1e59 100644 --- a/foundry.toml +++ b/foundry.toml @@ -17,6 +17,9 @@ int_types = "long" quote_style = "double" number_underscore = "preserve" override_spacing = true +ignore = [ + "src/lib/StorageLib.sol" +] [profile.test] no_match_test = "Fuzz" diff --git a/src/Dispatch.sol b/src/Dispatch.sol index 16a75f16..f485d408 100644 --- a/src/Dispatch.sol +++ b/src/Dispatch.sol @@ -7,8 +7,6 @@ import {HooksModule} from "./modules/Hooks.sol"; import {RewardsModule} from "./modules/Rewards.sol"; abstract contract Dispatch is RewardsModule, HooksModule { - error E_Unauthorized(); - address public immutable MODULE_REWARDS; address public immutable MODULE_HOOKS; address public immutable MODULE_FEE; diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 3d518e81..a6fc4c2c 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -1,27 +1,29 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {Shared} from "./Shared.sol"; -// external dep -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +// interfaces +import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; +import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; +import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; +import {Dispatch} from "./Dispatch.sol"; +// contracts import { ERC4626Upgradeable, - IERC4626, - ERC20Upgradeable, - Math + ERC20Upgradeable } from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; +import {Shared} from "./Shared.sol"; +import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgradeable.sol"; +// libs +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -// internal dep +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {StorageLib, AggregationVaultStorage, Strategy} from "./lib/StorageLib.sol"; import {ErrorsLib} from "./lib/ErrorsLib.sol"; -import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; -import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; -import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; -import {Dispatch} from "./Dispatch.sol"; -import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgradeable.sol"; +import {EventsLib} from "./lib/EventsLib.sol"; /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens @@ -45,15 +47,6 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea uint256 public constant INTEREST_SMEAR = 2 weeks; - event Gulp(uint256 interestLeft, uint256 interestSmearEnd); - event Harvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); - event AdjustAllocationPoints(address indexed strategy, uint256 oldPoints, uint256 newPoints); - event AddStrategy(address indexed strategy, uint256 allocationPoints); - event RemoveStrategy(address indexed _strategy); - event AccruePerformanceFee(address indexed feeRecipient, uint256 yield, uint256 feeAssets); - event SetStrategyCap(address indexed strategy, uint256 cap); - event Rebalance(address indexed strategy, uint256 _amountToRebalance, bool _isDeposit); - constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) Dispatch(_rewardsModule, _hooksModule, _feeModule, _allocationPointsModule) {} @@ -81,11 +74,10 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea __ERC4626_init_unchained(IERC20(_initParams.asset)); __ERC20_init_unchained(_initParams.name, _initParams.symbol); - _lock(); - if (_initParams.initialCashAllocationPoints == 0) revert ErrorsLib.InitialAllocationPointsZero(); AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + $.locked = REENTRANCYLOCK__UNLOCKED; $.withdrawalQueue = _initParams.withdrawalQueuePeriphery; $.strategies[address(0)] = Strategy({ allocated: 0, @@ -147,6 +139,53 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea /// @dev Should call the IBalanceTracker hook with the account's balance of 0 function disableBalanceForwarder() external override use(MODULE_REWARDS) {} + /// @notice Adjust a certain strategy's allocation points. + /// @dev Can only be called by an address that have the STRATEGY_MANAGER + /// @param _strategy address of strategy + /// @param _newPoints new strategy's points + function adjustAllocationPoints(address _strategy, uint256 _newPoints) + external + use(MODULE_ALLOCATION_POINTS) + onlyRole(STRATEGY_MANAGER) + {} + + /// @notice Set cap on strategy allocated amount. + /// @dev By default, cap is set to 0, not activated. + /// @param _strategy Strategy address. + /// @param _cap Cap amount + function setStrategyCap(address _strategy, uint256 _cap) + external + use(MODULE_ALLOCATION_POINTS) + onlyRole(STRATEGY_MANAGER) + {} + + /// @notice Add new strategy with it's allocation points. + /// @dev Can only be called by an address that have STRATEGY_ADDER. + /// @param _strategy Address of the strategy + /// @param _allocationPoints Strategy's allocation points + function addStrategy(address _strategy, uint256 _allocationPoints) + external + use(MODULE_ALLOCATION_POINTS) + onlyRole(STRATEGY_ADDER) + {} + + /// @notice Remove strategy and set its allocation points to zero. + /// @dev This function does not pull funds, `harvest()` needs to be called to withdraw + /// @dev Can only be called by an address that have the STRATEGY_REMOVER + /// @param _strategy Address of the strategy + function removeStrategy(address _strategy) external use(MODULE_ALLOCATION_POINTS) onlyRole(STRATEGY_REMOVER) {} + + /// @notice Set hooks contract and hooked functions. + /// @dev This funtion should be overriden to implement access control. + /// @param _hooksTarget Hooks contract. + /// @param _hookedFns Hooked functions. + function setHooksConfig(address _hooksTarget, uint32 _hookedFns) + external + override + onlyRole(MANAGER) + use(MODULE_HOOKS) + {} + /// @notice Harvest strategy. /// @param strategy address of strategy //TODO: is this safe without the reentrancy check @@ -186,45 +225,9 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea $.totalAllocated -= _amountToRebalance; } - emit Rebalance(_strategy, _amountToRebalance, _isDeposit); + emit EventsLib.Rebalance(_strategy, _amountToRebalance, _isDeposit); } - /// @notice Adjust a certain strategy's allocation points. - /// @dev Can only be called by an address that have the STRATEGY_MANAGER - /// @param _strategy address of strategy - /// @param _newPoints new strategy's points - function adjustAllocationPoints(address _strategy, uint256 _newPoints) - external - use(MODULE_ALLOCATION_POINTS) - onlyRole(STRATEGY_MANAGER) - {} - - /// @notice Set cap on strategy allocated amount. - /// @dev By default, cap is set to 0, not activated. - /// @param _strategy Strategy address. - /// @param _cap Cap amount - function setStrategyCap(address _strategy, uint256 _cap) - external - use(MODULE_ALLOCATION_POINTS) - onlyRole(STRATEGY_MANAGER) - {} - - /// @notice Add new strategy with it's allocation points. - /// @dev Can only be called by an address that have STRATEGY_ADDER. - /// @param _strategy Address of the strategy - /// @param _allocationPoints Strategy's allocation points - function addStrategy(address _strategy, uint256 _allocationPoints) - external - use(MODULE_ALLOCATION_POINTS) - onlyRole(STRATEGY_ADDER) - {} - - /// @notice Remove strategy and set its allocation points to zero. - /// @dev This function does not pull funds, `harvest()` needs to be called to withdraw - /// @dev Can only be called by an address that have the STRATEGY_REMOVER - /// @param _strategy Address of the strategy - function removeStrategy(address _strategy) external use(MODULE_ALLOCATION_POINTS) onlyRole(STRATEGY_REMOVER) {} - /// @notice update accrued interest function updateInterestAccrued() external { return _updateInterestAccrued(); @@ -235,6 +238,25 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea _gulp(); } + // TODO: add access control + function withdrawFromStrategy(address _strategy, uint256 _withdrawAmount) external { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + // Update allocated assets + $.strategies[_strategy].allocated -= uint120(_withdrawAmount); + $.totalAllocated -= _withdrawAmount; + + // Do actual withdraw from strategy + IERC4626(_strategy).withdraw(_withdrawAmount, address(this), address(this)); + } + + // TODO: add access control + function executeWithdrawFromReserve(address caller, address receiver, address owner, uint256 assets, uint256 shares) + external + { + _executeWithdrawFromReserve(caller, receiver, owner, assets, shares); + } + /// @notice Get strategy params. /// @param _strategy strategy's address /// @return Strategy struct @@ -316,30 +338,6 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea return super.redeem(shares, receiver, owner); } - /// @notice Set hooks contract and hooked functions. - /// @dev This funtion should be overriden to implement access control. - /// @param _hooksTarget Hooks contract. - /// @param _hookedFns Hooked functions. - function setHooksConfig(address _hooksTarget, uint32 _hookedFns) - external - override - onlyRole(MANAGER) - use(MODULE_HOOKS) - {} - - /// @notice update accrued interest. - function _updateInterestAccrued() internal { - uint256 accruedInterest = _interestAccruedFromCache(); - - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - // it's safe to down-cast because the accrued interest is a fraction of interest left - $.interestLeft -= uint168(accruedInterest); - $.lastInterestUpdate = uint40(block.timestamp); - - // Move interest accrued to totalAssetsDeposited - $.totalAssetsDeposited += accruedInterest; - } - /// @notice Return the total amount of assets deposited, plus the accrued interest. /// @return uint256 total amount function totalAssets() public view override returns (uint256) { @@ -393,26 +391,19 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea } } - // TODO: add access control - function withdrawFromStrategy(address _strategy, uint256 _withdrawAmount) external { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - // Update allocated assets - $.strategies[_strategy].allocated -= uint120(_withdrawAmount); - $.totalAllocated -= _withdrawAmount; + /// @notice update accrued interest. + function _updateInterestAccrued() internal { + uint256 accruedInterest = _interestAccruedFromCache(); - // Do actual withdraw from strategy - IERC4626(_strategy).withdraw(_withdrawAmount, address(this), address(this)); - } + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + // it's safe to down-cast because the accrued interest is a fraction of interest left + $.interestLeft -= uint168(accruedInterest); + $.lastInterestUpdate = uint40(block.timestamp); - // TODO: add access control - function executeWithdrawFromReserve(address caller, address receiver, address owner, uint256 assets, uint256 shares) - external - { - _executeWithdrawFromReserve(caller, receiver, owner, assets, shares); + // Move interest accrued to totalAssetsDeposited + $.totalAssetsDeposited += accruedInterest; } - // TODO: add access control function _executeWithdrawFromReserve( address caller, address receiver, @@ -442,7 +433,7 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea $.interestSmearEnd = uint40(block.timestamp + INTEREST_SMEAR); $.interestLeft += uint168(toGulp); // toGulp <= maxGulp <= max uint168 - emit Gulp($.interestLeft, $.interestSmearEnd); + emit EventsLib.Gulp($.interestLeft, $.interestSmearEnd); } function _harvest(address _strategy) internal { @@ -482,7 +473,7 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea } } - emit Harvest(_strategy, underlyingBalance, strategyAllocatedAmount); + emit EventsLib.Harvest(_strategy, underlyingBalance, strategyAllocatedAmount); } function _accruePerformanceFee(address _strategy, uint256 _yield) internal returns (uint120) { @@ -500,7 +491,7 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea IERC4626(_strategy).withdraw(feeAssets, cachedFeeRecipient, address(this)); } - emit AccruePerformanceFee(cachedFeeRecipient, _yield, feeAssets); + emit EventsLib.AccruePerformanceFee(cachedFeeRecipient, _yield, feeAssets); return feeAssets.toUint120(); } @@ -547,12 +538,6 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea return $.interestLeft * timePassed / totalDuration; } - function _lock() private onlyInitializing { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - $.locked = REENTRANCYLOCK__UNLOCKED; - } - function _msgSender() internal view override (ContextUpgradeable, Shared) returns (address) { return Shared._msgSender(); } diff --git a/src/Shared.sol b/src/Shared.sol index d9e29d19..8d8d9f2b 100644 --- a/src/Shared.sol +++ b/src/Shared.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {StorageLib, AggregationVaultStorage, HooksStorage} from "./lib/StorageLib.sol"; +import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; import {ErrorsLib} from "./lib/ErrorsLib.sol"; import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; @@ -21,8 +21,6 @@ contract Shared { uint32 constant ACTIONS_COUNTER = 1 << 4; uint256 constant HOOKS_MASK = 0x00000000000000000000000000000000000000000000000000000000FFFFFFFF; - event SetHooksConfig(address indexed hooksTarget, uint32 hookedFns); - modifier nonReentrant() { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); @@ -54,17 +52,15 @@ contract Shared { } if (_hookedFns >= ACTIONS_COUNTER) revert ErrorsLib.InvalidHookedFns(); - HooksStorage storage $ = StorageLib._getHooksStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.hooksConfig = (uint256(uint160(_hooksTarget)) << 32) | uint256(_hookedFns); - - emit SetHooksConfig(_hooksTarget, _hookedFns); } /// @notice Checks whether a hook has been installed for the function and if so, invokes the hook target. /// @param _fn Function to call the hook for. /// @param _caller Caller's address. function _callHooksTarget(uint32 _fn, address _caller) internal { - HooksStorage storage $ = StorageLib._getHooksStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); (address target, uint32 hookedFns) = _getHooksConfig($.hooksConfig); diff --git a/src/lib/EventsLib.sol b/src/lib/EventsLib.sol new file mode 100644 index 00000000..d8c9e8a2 --- /dev/null +++ b/src/lib/EventsLib.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +library EventsLib { + /// @dev FourSixTwoSixAgg events + event Gulp(uint256 interestLeft, uint256 interestSmearEnd); + event Harvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); + event AccruePerformanceFee(address indexed feeRecipient, uint256 yield, uint256 feeAssets); + event Rebalance(address indexed strategy, uint256 _amountToRebalance, bool _isDeposit); + + /// @dev Allocationpoints events + event AdjustAllocationPoints(address indexed strategy, uint256 oldPoints, uint256 newPoints); + event AddStrategy(address indexed strategy, uint256 allocationPoints); + event RemoveStrategy(address indexed _strategy); + event SetStrategyCap(address indexed strategy, uint256 cap); + + /// @dev Fee events + event SetFeeRecipient(address indexed oldRecipient, address indexed newRecipient); + event SetPerformanceFee(uint256 oldFee, uint256 newFee); + + /// @dev Hooks events + event SetHooksConfig(address indexed hooksTarget, uint32 hookedFns); + + /// @dev Rewards events + event OptInStrategyRewards(address indexed strategy); + event OptOutStrategyRewards(address indexed strategy); + event EnableBalanceForwarder(address indexed _user); + event DisableBalanceForwarder(address indexed _user); +} diff --git a/src/lib/StorageLib.sol b/src/lib/StorageLib.sol index e3f00b68..33435110 100644 --- a/src/lib/StorageLib.sol +++ b/src/lib/StorageLib.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; /// @custom:storage-location erc7201:euler_aggregation_vault.storage.AggregationVault struct AggregationVaultStorage { + /// @dev EVC address address evc; /// @dev Total amount of _asset deposited into FourSixTwoSixAgg contract uint256 totalAssetsDeposited; @@ -18,6 +19,8 @@ struct AggregationVaultStorage { address withdrawalQueue; /// @dev Mapping between strategy address and it's allocation config mapping(address => Strategy) strategies; + + /// lastInterestUpdate: last timestamo where interest was updated. uint40 lastInterestUpdate; /// interestSmearEnd: timestamp when the smearing of interest end. @@ -26,19 +29,16 @@ struct AggregationVaultStorage { uint168 interestLeft; /// locked: if locked or not for update. uint8 locked; -} -/// @custom:storage-location erc7201:euler_aggregation_vault.storage.BalanceForwarder -struct BalanceForwarderStorage { + address balanceTracker; mapping(address => bool) isBalanceForwarderEnabled; -} -/// @custom:storage-location erc7201:euler_aggregation_vault.storage.Hooks -struct HooksStorage { + /// @dev storing the hooks target and kooked functions. uint256 hooksConfig; } + /// @dev A struct that hold a strategy allocation's config /// allocated: amount of asset deposited into strategy /// allocationPoints: number of points allocated to this strategy @@ -56,27 +56,10 @@ library StorageLib { // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.AggregationVault")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant AggregationVaultStorageLocation = 0x7da5ece5aff94f3377324d715703a012d3253da37511270103c646f171c0aa00; - // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.BalanceForwarder")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant BalanceForwarderStorageLocation = - 0xf0af7cc3986d6cac468ac54c07f5f08359b3a00d65f8e2a86f2676ec79073f00; - // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.Hooks")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant HooksStorageLocation = 0x7daefa3ee1567d8892b825e51ce683a058a5785193bcc2ca50940db02ccbf700; function _getAggregationVaultStorage() internal pure returns (AggregationVaultStorage storage $) { assembly { $.slot := AggregationVaultStorageLocation } } - - function _getBalanceForwarderStorage() internal pure returns (BalanceForwarderStorage storage $) { - assembly { - $.slot := BalanceForwarderStorageLocation - } - } - - function _getHooksStorage() internal pure returns (HooksStorage storage $) { - assembly { - $.slot := HooksStorageLocation - } - } } diff --git a/src/modules/AllocationPoints.sol b/src/modules/AllocationPoints.sol index 6029741e..e4a27947 100644 --- a/src/modules/AllocationPoints.sol +++ b/src/modules/AllocationPoints.sol @@ -1,22 +1,20 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {Shared} from "../Shared.sol"; -// internal dep -import {StorageLib, AggregationVaultStorage, Strategy} from "../lib/StorageLib.sol"; -import {ErrorsLib} from "../lib/ErrorsLib.sol"; +// interfaces import {IERC4626} from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; import {IWithdrawalQueue} from "../interface/IWithdrawalQueue.sol"; +// contracts +import {Shared} from "../Shared.sol"; +// libs import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {StorageLib, AggregationVaultStorage, Strategy} from "../lib/StorageLib.sol"; +import {ErrorsLib} from "../lib/ErrorsLib.sol"; +import {EventsLib} from "../lib/EventsLib.sol"; abstract contract AllocationPointsModule is Shared { using SafeCast for uint256; - event AdjustAllocationPoints(address indexed strategy, uint256 oldPoints, uint256 newPoints); - event AddStrategy(address indexed strategy, uint256 allocationPoints); - event RemoveStrategy(address indexed _strategy); - event SetStrategyCap(address indexed strategy, uint256 cap); - /// @notice Adjust a certain strategy's allocation points. /// @dev Can only be called by an address that have the STRATEGY_MANAGER /// @param _strategy address of strategy @@ -33,7 +31,7 @@ abstract contract AllocationPointsModule is Shared { $.strategies[_strategy].allocationPoints = _newPoints.toUint120(); $.totalAllocationPoints = $.totalAllocationPoints + _newPoints - strategyDataCache.allocationPoints; - emit AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); + emit EventsLib.AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); } /// @notice Set cap on strategy allocated amount. @@ -49,7 +47,7 @@ abstract contract AllocationPointsModule is Shared { $.strategies[_strategy].cap = _cap.toUint120(); - emit SetStrategyCap(_strategy, _cap); + emit EventsLib.SetStrategyCap(_strategy, _cap); } /// @notice Add new strategy with it's allocation points. @@ -75,7 +73,7 @@ abstract contract AllocationPointsModule is Shared { $.totalAllocationPoints += _allocationPoints; IWithdrawalQueue($.withdrawalQueue).addStrategyToWithdrawalQueue(_strategy); - emit AddStrategy(_strategy, _allocationPoints); + emit EventsLib.AddStrategy(_strategy, _allocationPoints); } /// @notice Remove strategy and set its allocation points to zero. @@ -102,7 +100,7 @@ abstract contract AllocationPointsModule is Shared { // remove from withdrawalQueue IWithdrawalQueue($.withdrawalQueue).removeStrategyFromWithdrawalQueue(_strategy); - emit RemoveStrategy(_strategy); + emit EventsLib.RemoveStrategy(_strategy); } } diff --git a/src/modules/Fee.sol b/src/modules/Fee.sol index 6707efa5..c1436cdf 100644 --- a/src/modules/Fee.sol +++ b/src/modules/Fee.sol @@ -1,23 +1,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {Shared} from "../Shared.sol"; -import {StorageLib, BalanceForwarderStorage, AggregationVaultStorage} from "../lib/StorageLib.sol"; +// interfaces import {IBalanceForwarder} from "../interface/IBalanceForwarder.sol"; import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; -import {ErrorsLib} from "../lib/ErrorsLib.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +// contracts +import {Shared} from "../Shared.sol"; +// libs +import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; +import {ErrorsLib} from "../lib/ErrorsLib.sol"; +import {EventsLib} from "../lib/EventsLib.sol"; abstract contract FeeModule is Shared { - using StorageLib for *; - /// @dev The maximum performanceFee the vault can have is 50% uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; - event SetFeeRecipient(address indexed oldRecipient, address indexed newRecipient); - event SetPerformanceFee(uint256 oldFee, uint256 newFee); - /// @notice Set performance fee recipient address /// @notice @param _newFeeRecipient Recipient address function setFeeRecipient(address _newFeeRecipient) external { @@ -26,7 +25,7 @@ abstract contract FeeModule is Shared { if (_newFeeRecipient == feeRecipientCached) revert ErrorsLib.FeeRecipientAlreadySet(); - emit SetFeeRecipient(feeRecipientCached, _newFeeRecipient); + emit EventsLib.SetFeeRecipient(feeRecipientCached, _newFeeRecipient); $.feeRecipient = _newFeeRecipient; } @@ -42,7 +41,7 @@ abstract contract FeeModule is Shared { if ($.feeRecipient == address(0)) revert ErrorsLib.FeeRecipientNotSet(); if (_newFee == performanceFeeCached) revert ErrorsLib.PerformanceFeeAlreadySet(); - emit SetPerformanceFee(performanceFeeCached, _newFee); + emit EventsLib.SetPerformanceFee(performanceFeeCached, _newFee); $.performanceFee = _newFee; } diff --git a/src/modules/Hooks.sol b/src/modules/Hooks.sol index 8fee680b..3c799d2d 100644 --- a/src/modules/Hooks.sol +++ b/src/modules/Hooks.sol @@ -1,10 +1,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +// interfaces +import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; +// contracts import {Shared} from "../Shared.sol"; -import {StorageLib, HooksStorage} from "../lib/StorageLib.sol"; +// libs +import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {HooksLib} from "../lib/HooksLib.sol"; -import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; +import {EventsLib} from "../lib/EventsLib.sol"; abstract contract HooksModule is Shared { using HooksLib for uint32; @@ -14,13 +18,15 @@ abstract contract HooksModule is Shared { /// @param _hookedFns Hooked functions. function setHooksConfig(address _hooksTarget, uint32 _hookedFns) external virtual nonReentrant { _setHooksConfig(_hooksTarget, _hookedFns); + + emit EventsLib.SetHooksConfig(_hooksTarget, _hookedFns); } /// @notice Get the hooks contract and the hooked functions. /// @return address Hooks contract. /// @return uint32 Hooked functions. function getHooksConfig() external view returns (address, uint32) { - HooksStorage storage $ = StorageLib._getHooksStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return _getHooksConfig($.hooksConfig); } diff --git a/src/modules/Rewards.sol b/src/modules/Rewards.sol index 5afb4a72..663a680f 100644 --- a/src/modules/Rewards.sol +++ b/src/modules/Rewards.sol @@ -1,26 +1,24 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {Shared} from "../Shared.sol"; -import {StorageLib, BalanceForwarderStorage, AggregationVaultStorage} from "../lib/StorageLib.sol"; +// interfaces import {IBalanceForwarder} from "../interface/IBalanceForwarder.sol"; import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; -import {ErrorsLib} from "../lib/ErrorsLib.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +// contracts +import {Shared} from "../Shared.sol"; +// libs +import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; +import {ErrorsLib} from "../lib/ErrorsLib.sol"; +import {EventsLib} from "../lib/EventsLib.sol"; /// @title BalanceForwarder contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) /// @notice A generic contract to integrate with https://github.com/euler-xyz/reward-streams abstract contract RewardsModule is IBalanceForwarder, Shared { - using StorageLib for *; - - event OptInStrategyRewards(address indexed strategy); - event OptOutStrategyRewards(address indexed strategy); - event EnableBalanceForwarder(address indexed _user); - event DisableBalanceForwarder(address indexed _user); - + /// @notice Opt in to strategy rewards /// @param _strategy Strategy address function optInStrategyRewards(address _strategy) external virtual nonReentrant { @@ -30,7 +28,7 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { IBalanceForwarder(_strategy).enableBalanceForwarder(); - emit OptInStrategyRewards(_strategy); + emit EventsLib.OptInStrategyRewards(_strategy); } /// @notice Opt out of strategy rewards @@ -38,7 +36,7 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { function optOutStrategyRewards(address _strategy) external virtual nonReentrant { IBalanceForwarder(_strategy).disableBalanceForwarder(); - emit OptOutStrategyRewards(_strategy); + emit EventsLib.OptOutStrategyRewards(_strategy); } /// @notice Claim a specific strategy rewards @@ -76,7 +74,7 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { /// @notice Retrieve the address of rewards contract, tracking changes in account's balances /// @return The balance tracker address function balanceTrackerAddress() external view returns (address) { - BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return address($.balanceTracker); } @@ -89,7 +87,7 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { } function _enableBalanceForwarder(address _sender, uint256 _senderBalance) internal { - BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); IBalanceTracker balanceTrackerCached = IBalanceTracker($.balanceTracker); if (address(balanceTrackerCached) == address(0)) revert ErrorsLib.NotSupported(); @@ -98,14 +96,14 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { $.isBalanceForwarderEnabled[_sender] = true; balanceTrackerCached.balanceTrackerHook(_sender, _senderBalance, false); - emit EnableBalanceForwarder(_sender); + emit EventsLib.EnableBalanceForwarder(_sender); } /// @notice Disables balance forwarding for the authenticated account /// @dev Only the authenticated account can disable balance forwarding for itself /// @dev Should call the IBalanceTracker hook with the account's balance of 0 function _disableBalanceForwarder(address _sender) internal { - BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); IBalanceTracker balanceTrackerCached = IBalanceTracker($.balanceTracker); if (address(balanceTrackerCached) == address(0)) revert ErrorsLib.NotSupported(); @@ -114,11 +112,11 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { $.isBalanceForwarderEnabled[_sender] = false; balanceTrackerCached.balanceTrackerHook(_sender, 0, false); - emit DisableBalanceForwarder(_sender); + emit EventsLib.DisableBalanceForwarder(_sender); } function _setBalanceTracker(address _balancerTracker) internal { - BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.balanceTracker = _balancerTracker; } @@ -127,7 +125,7 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { /// @param _account Address to query /// @return True if balance forwarder is enabled function _balanceForwarderEnabled(address _account) internal view returns (bool) { - BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.isBalanceForwarderEnabled[_account]; } @@ -135,7 +133,7 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { /// @notice Retrieve the address of rewards contract, tracking changes in account's balances /// @return The balance tracker address function _balanceTrackerAddress() internal view returns (address) { - BalanceForwarderStorage storage $ = StorageLib._getBalanceForwarderStorage(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return address($.balanceTracker); } From 6e1a8fd167fa131b300583450d78ca195bf30983 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 18 Jun 2024 17:36:55 +0300 Subject: [PATCH 179/316] chore: rename roleS --- src/FourSixTwoSixAgg.sol | 56 ++++++++++++++------------ src/modules/AllocationPoints.sol | 2 +- src/modules/Rewards.sol | 1 - test/common/FourSixTwoSixAggBase.t.sol | 25 +++++++----- test/e2e/BalanceForwarderE2ETest.t.sol | 8 ++-- 5 files changed, 49 insertions(+), 43 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index a6fc4c2c..9ae27333 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -34,23 +34,20 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea using SafeCast for uint256; // Roles - bytes32 public constant STRATEGY_MANAGER = keccak256("STRATEGY_MANAGER"); - bytes32 public constant STRATEGY_MANAGER_ADMIN = keccak256("STRATEGY_MANAGER_ADMIN"); + bytes32 public constant ALLOCATIONS_MANAGER = keccak256("ALLOCATIONS_MANAGER"); + bytes32 public constant ALLOCATIONS_MANAGER_ADMIN = keccak256("ALLOCATIONS_MANAGER_ADMIN"); bytes32 public constant STRATEGY_ADDER = keccak256("STRATEGY_ADDER"); bytes32 public constant STRATEGY_ADDER_ADMIN = keccak256("STRATEGY_ADDER_ADMIN"); bytes32 public constant STRATEGY_REMOVER = keccak256("STRATEGY_REMOVER"); bytes32 public constant STRATEGY_REMOVER_ADMIN = keccak256("STRATEGY_REMOVER_ADMIN"); - bytes32 public constant MANAGER = keccak256("MANAGER"); - bytes32 public constant MANAGER_ADMIN = keccak256("MANAGER_ADMIN"); + bytes32 public constant AGGREGATION_VAULT_MANAGER = keccak256("AGGREGATION_VAULT_MANAGER"); + bytes32 public constant AGGREGATION_VAULT_MANAGER_ADMIN = keccak256("AGGREGATION_VAULT_MANAGER_ADMIN"); bytes32 public constant REBALANCER = keccak256("REBALANCER"); bytes32 public constant REBALANCER_ADMIN = keccak256("REBALANCER_ADMIN"); + /// @dev interest rate smearing period uint256 public constant INTEREST_SMEAR = 2 weeks; - constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) - Dispatch(_rewardsModule, _hooksModule, _feeModule, _allocationPointsModule) - {} - struct InitParams { address evc; address balanceTracker; @@ -62,13 +59,10 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea string symbol; uint256 initialCashAllocationPoints; } - // /// @param _evc EVC address - // /// @param _asset Aggregator's asset address - // /// @param _name Aggregator's name - // /// @param _symbol Aggregator's symbol - // /// @param _initialCashAllocationPoints Initial points to be allocated to the cash reserve - // /// @param _initialStrategies An array of initial strategies addresses - // /// @param _initialStrategiesAllocationPoints An array of initial strategies allocation points + + constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) + Dispatch(_rewardsModule, _hooksModule, _feeModule, _allocationPointsModule) + {} function init(InitParams calldata _initParams) external initializer { __ERC4626_init_unchained(IERC20(_initParams.asset)); @@ -96,28 +90,38 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea _grantRole(REBALANCER, _initParams.rebalancerPerihpery); // Setup role admins - _setRoleAdmin(STRATEGY_MANAGER, STRATEGY_MANAGER_ADMIN); + _setRoleAdmin(ALLOCATIONS_MANAGER, ALLOCATIONS_MANAGER_ADMIN); _setRoleAdmin(STRATEGY_ADDER, STRATEGY_ADDER_ADMIN); _setRoleAdmin(STRATEGY_REMOVER, STRATEGY_REMOVER_ADMIN); - _setRoleAdmin(MANAGER, MANAGER_ADMIN); + _setRoleAdmin(AGGREGATION_VAULT_MANAGER, AGGREGATION_VAULT_MANAGER_ADMIN); _setRoleAdmin(REBALANCER, REBALANCER_ADMIN); } /// @notice Set performance fee recipient address /// @notice @param _newFeeRecipient Recipient address - function setFeeRecipient(address _newFeeRecipient) external onlyRole(MANAGER) use(MODULE_FEE) {} + function setFeeRecipient(address _newFeeRecipient) external onlyRole(AGGREGATION_VAULT_MANAGER) use(MODULE_FEE) {} /// @notice Set performance fee (1e18 == 100%) /// @notice @param _newFee Fee rate - function setPerformanceFee(uint256 _newFee) external onlyRole(MANAGER) use(MODULE_FEE) {} + function setPerformanceFee(uint256 _newFee) external onlyRole(AGGREGATION_VAULT_MANAGER) use(MODULE_FEE) {} /// @notice Opt in to strategy rewards /// @param _strategy Strategy address - function optInStrategyRewards(address _strategy) external override onlyRole(MANAGER) use(MODULE_REWARDS) {} + function optInStrategyRewards(address _strategy) + external + override + onlyRole(AGGREGATION_VAULT_MANAGER) + use(MODULE_REWARDS) + {} /// @notice Opt out of strategy rewards /// @param _strategy Strategy address - function optOutStrategyRewards(address _strategy) external override onlyRole(MANAGER) use(MODULE_REWARDS) {} + function optOutStrategyRewards(address _strategy) + external + override + onlyRole(AGGREGATION_VAULT_MANAGER) + use(MODULE_REWARDS) + {} /// @notice Claim a specific strategy rewards /// @param _strategy Strategy address. @@ -127,7 +131,7 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea function claimStrategyReward(address _strategy, address _reward, address _recipient, bool _forfeitRecentReward) external override - onlyRole(MANAGER) + onlyRole(AGGREGATION_VAULT_MANAGER) use(MODULE_REWARDS) {} @@ -140,13 +144,13 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea function disableBalanceForwarder() external override use(MODULE_REWARDS) {} /// @notice Adjust a certain strategy's allocation points. - /// @dev Can only be called by an address that have the STRATEGY_MANAGER + /// @dev Can only be called by an address that have the ALLOCATIONS_MANAGER /// @param _strategy address of strategy /// @param _newPoints new strategy's points function adjustAllocationPoints(address _strategy, uint256 _newPoints) external use(MODULE_ALLOCATION_POINTS) - onlyRole(STRATEGY_MANAGER) + onlyRole(ALLOCATIONS_MANAGER) {} /// @notice Set cap on strategy allocated amount. @@ -156,7 +160,7 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea function setStrategyCap(address _strategy, uint256 _cap) external use(MODULE_ALLOCATION_POINTS) - onlyRole(STRATEGY_MANAGER) + onlyRole(ALLOCATIONS_MANAGER) {} /// @notice Add new strategy with it's allocation points. @@ -182,7 +186,7 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea function setHooksConfig(address _hooksTarget, uint32 _hookedFns) external override - onlyRole(MANAGER) + onlyRole(AGGREGATION_VAULT_MANAGER) use(MODULE_HOOKS) {} diff --git a/src/modules/AllocationPoints.sol b/src/modules/AllocationPoints.sol index e4a27947..439735b6 100644 --- a/src/modules/AllocationPoints.sol +++ b/src/modules/AllocationPoints.sol @@ -16,7 +16,7 @@ abstract contract AllocationPointsModule is Shared { using SafeCast for uint256; /// @notice Adjust a certain strategy's allocation points. - /// @dev Can only be called by an address that have the STRATEGY_MANAGER + /// @dev Can only be called by an address that have the ALLOCATIONS_MANAGER /// @param _strategy address of strategy /// @param _newPoints new strategy's points function adjustAllocationPoints(address _strategy, uint256 _newPoints) external virtual nonReentrant { diff --git a/src/modules/Rewards.sol b/src/modules/Rewards.sol index 663a680f..24fc79fd 100644 --- a/src/modules/Rewards.sol +++ b/src/modules/Rewards.sol @@ -18,7 +18,6 @@ import {EventsLib} from "../lib/EventsLib.sol"; /// @author Euler Labs (https://www.eulerlabs.com/) /// @notice A generic contract to integrate with https://github.com/euler-xyz/reward-streams abstract contract RewardsModule is IBalanceForwarder, Shared { - /// @notice Opt in to strategy rewards /// @param _strategy Strategy address function optInStrategyRewards(address _strategy) external virtual nonReentrant { diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/FourSixTwoSixAggBase.t.sol index 0268dcd7..acb583e7 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/FourSixTwoSixAggBase.t.sol @@ -70,18 +70,18 @@ contract FourSixTwoSixAggBase is EVaultTestBase { withdrawalQueue = WithdrawalQueue(fourSixTwoSixAgg.withdrawalQueue()); // grant admin roles to deployer - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATIONS_MANAGER_ADMIN(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER_ADMIN(), deployer); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATIONS_MANAGER(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER(), manager); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); vm.stopPrank(); @@ -95,8 +95,8 @@ contract FourSixTwoSixAggBase is EVaultTestBase { assertEq(cashReserve.active, true); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_MANAGER()), - fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN() + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.ALLOCATIONS_MANAGER()), + fourSixTwoSixAgg.ALLOCATIONS_MANAGER_ADMIN() ); assertEq( fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_ADDER()), fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN() @@ -105,22 +105,25 @@ contract FourSixTwoSixAggBase is EVaultTestBase { fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_REMOVER()), fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN() ); - assertEq(fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.MANAGER()), fourSixTwoSixAgg.MANAGER_ADMIN()); + assertEq( + fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER()), + fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER_ADMIN() + ); assertEq( withdrawalQueue.getRoleAdmin(withdrawalQueue.WITHDRAW_QUEUE_MANAGER()), withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN() ); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATIONS_MANAGER_ADMIN(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER_ADMIN(), deployer)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_MANAGER(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATIONS_MANAGER(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager)); assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.MANAGER(), manager)); + assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER(), manager)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); } diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 9e45b94b..e2c5c8da 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -42,18 +42,18 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { ); // grant admin roles to deployer - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATIONS_MANAGER_ADMIN(), deployer); // fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER_ADMIN(), deployer); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); // grant roles to manager - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_MANAGER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATIONS_MANAGER(), manager); // fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager); fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.MANAGER(), manager); + fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; From 133865d9b3596f567f49f437723da4fda96a48fc Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 18 Jun 2024 18:46:53 +0300 Subject: [PATCH 180/316] add access control & last touches --- src/FourSixTwoSixAgg.sol | 65 +++++++++++-------- src/FourSixTwoSixAggFactory.sol | 1 - src/WithdrawalQueue.sol | 96 +++++++++++++++++------------ src/interface/IFourSixTwoSixAgg.sol | 11 +++- src/interface/IWithdrawalQueue.sol | 2 +- src/lib/ErrorsLib.sol | 1 + 6 files changed, 103 insertions(+), 73 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 9ae27333..4fc4a673 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -242,8 +242,13 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea _gulp(); } - // TODO: add access control - function withdrawFromStrategy(address _strategy, uint256 _withdrawAmount) external { + /// @notice Execute a withdraw from a strategy. + /// @dev Can only be called from the WithdrawalQueue contract. + /// @param _strategy Strategy's address. + /// @param _withdrawAmount Amount to withdraw. + function executeStrategyWithdraw(address _strategy, uint256 _withdrawAmount) external { + _isCallerWithdrawalQueue(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); // Update allocated assets @@ -254,11 +259,28 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea IERC4626(_strategy).withdraw(_withdrawAmount, address(this), address(this)); } - // TODO: add access control - function executeWithdrawFromReserve(address caller, address receiver, address owner, uint256 assets, uint256 shares) - external - { - _executeWithdrawFromReserve(caller, receiver, owner, assets, shares); + /// @notice Execute a withdraw from the AggregationVault + /// @dev This function should be called and can only be called by the WithdrawalQueue. + /// @param caller Withdraw call initiator. + /// @param receiver Receiver of the withdrawn asset. + /// @param owner Owner of shares to withdraw against. + /// @param assets Amount of asset to withdraw. + /// @param shares Amount of shares to withdraw against. + function executeAggregationVaultWithdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) external { + _isCallerWithdrawalQueue(); + + _gulp(); + + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + $.totalAssetsDeposited -= assets; + + super._withdraw(caller, receiver, owner, assets, shares); } /// @notice Get strategy params. @@ -383,16 +405,11 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - $.totalAssetsDeposited -= assets; uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); - if (assetsRetrieved < assets) { - IWithdrawalQueue($.withdrawalQueue).executeWithdrawFromQueue( - caller, receiver, owner, assets, shares, assetsRetrieved - ); - } else { - _executeWithdrawFromReserve(caller, receiver, owner, assets, shares); - } + IWithdrawalQueue($.withdrawalQueue).callWithdrawalQueue( + caller, receiver, owner, assets, shares, assetsRetrieved + ); } /// @notice update accrued interest. @@ -408,18 +425,6 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea $.totalAssetsDeposited += accruedInterest; } - function _executeWithdrawFromReserve( - address caller, - address receiver, - address owner, - uint256 assets, - uint256 shares - ) internal { - _gulp(); - - super._withdraw(caller, receiver, owner, assets, shares); - } - /// @dev gulp positive yield and increment the left interest function _gulp() internal { _updateInterestAccrued(); @@ -545,4 +550,10 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea function _msgSender() internal view override (ContextUpgradeable, Shared) returns (address) { return Shared._msgSender(); } + + function _isCallerWithdrawalQueue() internal view { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if (_msgSender() != $.withdrawalQueue) revert ErrorsLib.NotWithdrawaQueue(); + } } diff --git a/src/FourSixTwoSixAggFactory.sol b/src/FourSixTwoSixAggFactory.sol index 00465d44..713d5188 100644 --- a/src/FourSixTwoSixAggFactory.sol +++ b/src/FourSixTwoSixAggFactory.sol @@ -46,7 +46,6 @@ contract FourSixTwoSixAggFactory { withdrawalQueueAddr = _withdrawalQueueAddr; } - // TODO: decrease bytecode size, use clones or something function deployEulerAggregationLayer( address _asset, string memory _name, diff --git a/src/WithdrawalQueue.sol b/src/WithdrawalQueue.sol index 1dd7730b..5c67d253 100644 --- a/src/WithdrawalQueue.sol +++ b/src/WithdrawalQueue.sol @@ -12,6 +12,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { error OutOfBounds(); error SameIndexes(); error NotEnoughAssets(); + error NotAuthorized(); bytes32 public constant WITHDRAW_QUEUE_MANAGER = keccak256("WITHDRAW_QUEUE_MANAGER"); bytes32 public constant WITHDRAW_QUEUE_MANAGER_ADMIN = keccak256("WITHDRAW_QUEUE_MANAGER_ADMIN"); @@ -37,21 +38,17 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { _setRoleAdmin(WITHDRAW_QUEUE_MANAGER, WITHDRAW_QUEUE_MANAGER_ADMIN); } - function getWithdrawalQueueAtIndex(uint256 _index) external view returns (address) { - WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - - return $.withdrawalQueue[_index]; - } - - // TODO: add access control function addStrategyToWithdrawalQueue(address _strategy) external { + _isCallerAggregationVault(); + WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); $.withdrawalQueue.push(_strategy); } - // TODO: add access control function removeStrategyFromWithdrawalQueue(address _strategy) external { + _isCallerAggregationVault(); + WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); uint256 lastStrategyIndex = $.withdrawalQueue.length - 1; @@ -67,8 +64,29 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { $.withdrawalQueue.pop(); } - // TODO: add access control - function executeWithdrawFromQueue( + /// @notice Swap two strategies indexes in the withdrawal queue. + /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER. + /// @param _index1 index of first strategy + /// @param _index2 index of second strategy + function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external onlyRole(WITHDRAW_QUEUE_MANAGER) { + WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); + + uint256 length = $.withdrawalQueue.length; + if (_index1 >= length || _index2 >= length) { + revert OutOfBounds(); + } + + if (_index1 == _index2) { + revert SameIndexes(); + } + + ($.withdrawalQueue[_index1], $.withdrawalQueue[_index2]) = + ($.withdrawalQueue[_index2], $.withdrawalQueue[_index1]); + + emit ReorderWithdrawalQueue(_index1, _index2); + } + + function callWithdrawalQueue( address caller, address receiver, address owner, @@ -76,26 +94,32 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { uint256 shares, uint256 availableAssets ) external { + _isCallerAggregationVault(); + WithdrawalQueueStorage memory $ = _getWithdrawalQueueStorage(); address eulerAggregationVaultCached = $.eulerAggregationVault; - uint256 numStrategies = $.withdrawalQueue.length; - for (uint256 i; i < numStrategies; ++i) { - IERC4626 strategy = IERC4626($.withdrawalQueue[i]); + if (availableAssets < assets) { + uint256 numStrategies = $.withdrawalQueue.length; + for (uint256 i; i < numStrategies; ++i) { + IERC4626 strategy = IERC4626($.withdrawalQueue[i]); - IFourSixTwoSixAgg(eulerAggregationVaultCached).harvest(address(strategy)); + IFourSixTwoSixAgg(eulerAggregationVaultCached).harvest(address(strategy)); - uint256 underlyingBalance = strategy.maxWithdraw(eulerAggregationVaultCached); - uint256 desiredAssets = assets - availableAssets; - uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; + uint256 underlyingBalance = strategy.maxWithdraw(eulerAggregationVaultCached); + uint256 desiredAssets = assets - availableAssets; + uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; - IFourSixTwoSixAgg(eulerAggregationVaultCached).withdrawFromStrategy(address(strategy), withdrawAmount); + IFourSixTwoSixAgg(eulerAggregationVaultCached).executeStrategyWithdraw( + address(strategy), withdrawAmount + ); - // update assetsRetrieved - availableAssets += withdrawAmount; + // update assetsRetrieved + availableAssets += withdrawAmount; - if (availableAssets >= assets) { - break; + if (availableAssets >= assets) { + break; + } } } @@ -103,31 +127,15 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { revert NotEnoughAssets(); } - IFourSixTwoSixAgg(eulerAggregationVaultCached).executeWithdrawFromReserve( + IFourSixTwoSixAgg(eulerAggregationVaultCached).executeAggregationVaultWithdraw( caller, receiver, owner, assets, shares ); } - /// @notice Swap two strategies indexes in the withdrawal queue. - /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER. - /// @param _index1 index of first strategy - /// @param _index2 index of second strategy - function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external onlyRole(WITHDRAW_QUEUE_MANAGER) { + function getWithdrawalQueueAtIndex(uint256 _index) external view returns (address) { WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - uint256 length = $.withdrawalQueue.length; - if (_index1 >= length || _index2 >= length) { - revert OutOfBounds(); - } - - if (_index1 == _index2) { - revert SameIndexes(); - } - - ($.withdrawalQueue[_index1], $.withdrawalQueue[_index2]) = - ($.withdrawalQueue[_index2], $.withdrawalQueue[_index1]); - - emit ReorderWithdrawalQueue(_index1, _index2); + return $.withdrawalQueue[_index]; } /// @notice Return the withdrawal queue length. @@ -138,6 +146,12 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { return $.withdrawalQueue.length; } + function _isCallerAggregationVault() private view { + WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); + + if (msg.sender != $.eulerAggregationVault) revert NotAuthorized(); + } + function _getWithdrawalQueueStorage() private pure returns (WithdrawalQueueStorage storage $) { assembly { $.slot := WithdrawalQueueStorageLocation diff --git a/src/interface/IFourSixTwoSixAgg.sol b/src/interface/IFourSixTwoSixAgg.sol index 773b3d23..30300a6f 100644 --- a/src/interface/IFourSixTwoSixAgg.sol +++ b/src/interface/IFourSixTwoSixAgg.sol @@ -7,9 +7,14 @@ interface IFourSixTwoSixAgg { function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external; function gulp() external; function harvest(address strategy) external; - function withdrawFromStrategy(address _strategy, uint256 _withdrawAmount) external; - function executeWithdrawFromReserve(address caller, address receiver, address owner, uint256 assets, uint256 shares) - external; + function executeStrategyWithdraw(address _strategy, uint256 _withdrawAmount) external; + function executeAggregationVaultWithdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) external; function getStrategy(address _strategy) external view returns (Strategy memory); function totalAllocationPoints() external view returns (uint256); function totalAllocated() external view returns (uint256); diff --git a/src/interface/IWithdrawalQueue.sol b/src/interface/IWithdrawalQueue.sol index 1bf65969..e5e68d0c 100644 --- a/src/interface/IWithdrawalQueue.sol +++ b/src/interface/IWithdrawalQueue.sol @@ -5,7 +5,7 @@ interface IWithdrawalQueue { function init(address _owner, address _eulerAggregationVault) external; function addStrategyToWithdrawalQueue(address _strategy) external; function removeStrategyFromWithdrawalQueue(address _strategy) external; - function executeWithdrawFromQueue( + function callWithdrawalQueue( address caller, address receiver, address owner, diff --git a/src/lib/ErrorsLib.sol b/src/lib/ErrorsLib.sol index d7599b39..b7ed440b 100644 --- a/src/lib/ErrorsLib.sol +++ b/src/lib/ErrorsLib.sol @@ -23,4 +23,5 @@ library ErrorsLib { error NotHooksContract(); error InvalidHookedFns(); error EmptyError(); + error NotWithdrawaQueue(); } From c4d83c2fb978d92efad949c3bccca5d7235087aa Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 18 Jun 2024 18:58:51 +0300 Subject: [PATCH 181/316] chore: uncomment tests --- src/FourSixTwoSixAgg.sol | 24 +++ test/unit/GulpTest.t.sol | 314 ++++++++++++++++++++------------------- 2 files changed, 182 insertions(+), 156 deletions(-) diff --git a/src/FourSixTwoSixAgg.sol b/src/FourSixTwoSixAgg.sol index 4fc4a673..e814d251 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/FourSixTwoSixAgg.sol @@ -48,6 +48,18 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea /// @dev interest rate smearing period uint256 public constant INTEREST_SMEAR = 2 weeks; + /// @dev Euler saving rate struct + /// lastInterestUpdate: last timestamo where interest was updated. + /// interestSmearEnd: timestamp when the smearing of interest end. + /// interestLeft: amount of interest left to smear. + /// locked: if locked or not for update. + struct AggregationVaultSavingRate { + uint40 lastInterestUpdate; + uint40 interestSmearEnd; + uint168 interestLeft; + uint8 locked; + } + struct InitParams { address evc; address balanceTracker; @@ -298,6 +310,18 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea return _interestAccruedFromCache(); } + function getAggregationVaultSavingRate() external view returns (AggregationVaultSavingRate memory) { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultSavingRate memory avsr = AggregationVaultSavingRate({ + lastInterestUpdate: $.lastInterestUpdate, + interestSmearEnd: $.interestSmearEnd, + interestLeft: $.interestLeft, + locked: $.locked + }); + + return avsr; + } + function totalAllocated() external view returns (uint256) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index d6c383f1..fca77edb 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -1,156 +1,158 @@ -// // SPDX-License-Identifier: GPL-2.0-or-later -// pragma solidity ^0.8.0; - -// import {FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault, Strategy} from "../common/FourSixTwoSixAggBase.t.sol"; - -// contract GulpTest is FourSixTwoSixAggBase { -// uint256 user1InitialBalance = 100000e18; -// uint256 amountToDeposit = 10000e18; - -// function setUp() public virtual override { -// super.setUp(); - -// uint256 initialStrategyAllocationPoints = 500e18; -// _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); - -// assetTST.mint(user1, user1InitialBalance); - -// // deposit into aggregator -// { -// uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); -// uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); -// uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); -// uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); - -// vm.startPrank(user1); -// assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); -// fourSixTwoSixAgg.deposit(amountToDeposit, user1); -// vm.stopPrank(); - -// assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); -// assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); -// assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); -// assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); -// } - -// // rebalance into strategy -// vm.warp(block.timestamp + 86400); -// { -// Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); - -// assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); - -// uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints -// / fourSixTwoSixAgg.totalAllocationPoints(); - -// vm.prank(user1); -// address[] memory strategiesToRebalance = new address[](1); -// strategiesToRebalance[0] = address(eTST); -// rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); - -// assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); -// assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); -// assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); -// } -// } - -// function testGulpAfterNegativeYieldEqualToInterestLeft() public { -// fourSixTwoSixAgg.gulp(); -// FourSixTwoSixAgg.AggregationVaultSavingRateStorage memory ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); -// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); -// assertEq(ers.interestLeft, 0); - -// vm.warp(block.timestamp + 2 days); -// fourSixTwoSixAgg.gulp(); -// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - -// vm.warp(block.timestamp + 1 days); -// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); -// uint256 yield; -// { -// uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); -// uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); -// uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; -// yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; -// assetTST.mint(address(eTST), yield); -// eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); -// } -// vm.prank(user1); -// fourSixTwoSixAgg.harvest(address(eTST)); - -// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - -// vm.warp(block.timestamp + 1 days); -// // interest per day 23.809523809523 -// assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); -// fourSixTwoSixAgg.gulp(); -// ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); -// assertEq(ers.interestLeft, yield - 23809523809523809523); - -// // move close to end of smearing -// vm.warp(block.timestamp + 11 days); -// fourSixTwoSixAgg.gulp(); -// ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); - -// // mock a decrease of strategy balance by ers.interestLeft -// uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); -// uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - ers.interestLeft; -// vm.mockCall( -// address(eTST), -// abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), -// abi.encode(aggrCurrentStrategyBalanceAfterNegYield) -// ); -// vm.prank(user1); -// fourSixTwoSixAgg.harvest(address(eTST)); -// } - -// function testGulpAfterNegativeYieldBiggerThanInterestLeft() public { -// fourSixTwoSixAgg.gulp(); -// FourSixTwoSixAgg.AggregationVaultSavingRateStorage memory ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); -// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); -// assertEq(ers.interestLeft, 0); - -// vm.warp(block.timestamp + 2 days); -// fourSixTwoSixAgg.gulp(); -// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - -// vm.warp(block.timestamp + 1 days); -// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); -// uint256 yield; -// { -// uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); -// uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); -// uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; -// yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; -// assetTST.mint(address(eTST), yield); -// eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); -// } -// vm.prank(user1); -// fourSixTwoSixAgg.harvest(address(eTST)); - -// assertEq(fourSixTwoSixAgg.interestAccrued(), 0); - -// vm.warp(block.timestamp + 1 days); -// // interest per day 23.809523809523 -// assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); -// fourSixTwoSixAgg.gulp(); -// ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); -// assertEq(ers.interestLeft, yield - 23809523809523809523); - -// // move close to end of smearing -// vm.warp(block.timestamp + 11 days); -// fourSixTwoSixAgg.gulp(); -// ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); - -// // mock a decrease of strategy balance by ers.interestLeft -// uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); -// uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - (ers.interestLeft * 2); -// vm.mockCall( -// address(eTST), -// abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), -// abi.encode(aggrCurrentStrategyBalanceAfterNegYield) -// ); -// vm.prank(user1); -// fourSixTwoSixAgg.harvest(address(eTST)); -// } -// } +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault, Strategy +} from "../common/FourSixTwoSixAggBase.t.sol"; + +contract GulpTest is FourSixTwoSixAggBase { + uint256 user1InitialBalance = 100000e18; + uint256 amountToDeposit = 10000e18; + + function setUp() public virtual override { + super.setUp(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + + assetTST.mint(user1, user1InitialBalance); + + // deposit into aggregator + { + uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); + uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); + fourSixTwoSixAgg.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + + uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints + / fourSixTwoSixAgg.totalAllocationPoints(); + + vm.prank(user1); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + + assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + } + } + + function testGulpAfterNegativeYieldEqualToInterestLeft() public { + fourSixTwoSixAgg.gulp(); + FourSixTwoSixAgg.AggregationVaultSavingRate memory ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + assertEq(ers.interestLeft, 0); + + vm.warp(block.timestamp + 2 days); + fourSixTwoSixAgg.gulp(); + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + + vm.warp(block.timestamp + 1 days); + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + uint256 yield; + { + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); + uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; + yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; + assetTST.mint(address(eTST), yield); + eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + } + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + + vm.warp(block.timestamp + 1 days); + // interest per day 23.809523809523 + assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); + fourSixTwoSixAgg.gulp(); + ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); + assertEq(ers.interestLeft, yield - 23809523809523809523); + + // move close to end of smearing + vm.warp(block.timestamp + 11 days); + fourSixTwoSixAgg.gulp(); + ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); + + // mock a decrease of strategy balance by ers.interestLeft + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - ers.interestLeft; + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encode(aggrCurrentStrategyBalanceAfterNegYield) + ); + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + } + + function testGulpAfterNegativeYieldBiggerThanInterestLeft() public { + fourSixTwoSixAgg.gulp(); + FourSixTwoSixAgg.AggregationVaultSavingRate memory ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + assertEq(ers.interestLeft, 0); + + vm.warp(block.timestamp + 2 days); + fourSixTwoSixAgg.gulp(); + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + + vm.warp(block.timestamp + 1 days); + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + uint256 yield; + { + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); + uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; + yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; + assetTST.mint(address(eTST), yield); + eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + } + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + + assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + + vm.warp(block.timestamp + 1 days); + // interest per day 23.809523809523 + assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); + fourSixTwoSixAgg.gulp(); + ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); + assertEq(ers.interestLeft, yield - 23809523809523809523); + + // move close to end of smearing + vm.warp(block.timestamp + 11 days); + fourSixTwoSixAgg.gulp(); + ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); + + // mock a decrease of strategy balance by ers.interestLeft + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - (ers.interestLeft * 2); + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encode(aggrCurrentStrategyBalanceAfterNegYield) + ); + vm.prank(user1); + fourSixTwoSixAgg.harvest(address(eTST)); + } +} From 680dd20e9137b07402a870d80d88c0fca128cb93 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 19 Jun 2024 12:03:03 +0300 Subject: [PATCH 182/316] chore: natspec and renames --- ...woSixAgg.sol => AggregationLayerVault.sol} | 16 +- ...y.sol => AggregationLayerVaultFactory.sol} | 22 +- src/WithdrawalQueue.sol | 41 +- src/lib/EventsLib.sol | 2 +- src/lib/StorageLib.sol | 2 +- ....t.sol => AggregationLayerVaultBase.t.sol} | 74 ++-- test/e2e/BalanceForwarderE2ETest.t.sol | 134 +++--- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 389 +++++++++--------- test/e2e/HooksE2ETest.t.sol | 50 +-- test/e2e/PerformanceFeeE2ETest.t.sol | 98 ++--- test/e2e/StrategyCapE2ETest.t.sol | 95 ++--- test/e2e/StrategyRewardsE2ETest.t.sol | 20 +- .../fuzz/AdjustAllocationPointsFuzzTest.t.sol | 16 +- .../DepositWithdrawMintBurnFuzzTest.t.sol | 68 +-- test/unit/AddStrategyTest.t.sol | 12 +- test/unit/AdjustAllocationPointsTest.t.sol | 23 +- test/unit/GulpTest.t.sol | 118 +++--- test/unit/HarvestTest.t.sol | 139 ++++--- test/unit/RebalanceTest.t.sol | 223 +++++----- test/unit/RemoveStrategy.t.sol | 39 +- test/unit/ReorderWithdrawalQueueTest.t.sol | 23 +- 21 files changed, 844 insertions(+), 760 deletions(-) rename src/{FourSixTwoSixAgg.sol => AggregationLayerVault.sol} (98%) rename src/{FourSixTwoSixAggFactory.sol => AggregationLayerVaultFactory.sol} (80%) rename test/common/{FourSixTwoSixAggBase.t.sol => AggregationLayerVaultBase.t.sol} (53%) diff --git a/src/FourSixTwoSixAgg.sol b/src/AggregationLayerVault.sol similarity index 98% rename from src/FourSixTwoSixAgg.sol rename to src/AggregationLayerVault.sol index e814d251..074ac10e 100644 --- a/src/FourSixTwoSixAgg.sol +++ b/src/AggregationLayerVault.sol @@ -7,8 +7,8 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; -import {Dispatch} from "./Dispatch.sol"; // contracts +import {Dispatch} from "./Dispatch.sol"; import { ERC4626Upgradeable, ERC20Upgradeable @@ -27,9 +27,13 @@ import {EventsLib} from "./lib/EventsLib.sol"; /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens -/// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol /// @dev inspired by Yearn v3 ❤️ -contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradeable, Dispatch, IFourSixTwoSixAgg { +contract AggregationLayerVault is + ERC4626Upgradeable, + AccessControlEnumerableUpgradeable, + Dispatch, + IFourSixTwoSixAgg +{ using SafeERC20 for IERC20; using SafeCast for uint256; @@ -49,6 +53,7 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea uint256 public constant INTEREST_SMEAR = 2 weeks; /// @dev Euler saving rate struct + /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol /// lastInterestUpdate: last timestamo where interest was updated. /// interestSmearEnd: timestamp when the smearing of interest end. /// interestLeft: amount of interest left to smear. @@ -204,7 +209,6 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea /// @notice Harvest strategy. /// @param strategy address of strategy - //TODO: is this safe without the reentrancy check function harvest(address strategy) external { _harvest(strategy); @@ -287,12 +291,12 @@ contract FourSixTwoSixAgg is ERC4626Upgradeable, AccessControlEnumerableUpgradea ) external { _isCallerWithdrawalQueue(); - _gulp(); - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.totalAssetsDeposited -= assets; super._withdraw(caller, receiver, owner, assets, shares); + + _gulp(); } /// @notice Get strategy params. diff --git a/src/FourSixTwoSixAggFactory.sol b/src/AggregationLayerVaultFactory.sol similarity index 80% rename from src/FourSixTwoSixAggFactory.sol rename to src/AggregationLayerVaultFactory.sol index 713d5188..e105e3bd 100644 --- a/src/FourSixTwoSixAggFactory.sol +++ b/src/AggregationLayerVaultFactory.sol @@ -6,11 +6,11 @@ import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; import {Rewards} from "./modules/Rewards.sol"; import {Hooks} from "./modules/Hooks.sol"; import {Fee} from "./modules/Fee.sol"; -// core peripheries +// core plugins import {WithdrawalQueue} from "./WithdrawalQueue.sol"; -import {FourSixTwoSixAgg} from "./FourSixTwoSixAgg.sol"; +import {AggregationLayerVault} from "./AggregationLayerVault.sol"; -contract FourSixTwoSixAggFactory { +contract AggregationLayerVaultFactory { /// core dependencies address public immutable evc; address public immutable balanceTracker; @@ -19,7 +19,7 @@ contract FourSixTwoSixAggFactory { address public immutable hooksModuleImpl; address public immutable feeModuleImpl; address public immutable allocationpointsModuleImpl; - /// peripheries + /// plugins /// @dev Rebalancer periphery, one instance can serve different aggregation vaults address public immutable rebalancerAddr; /// @dev Withdrawal queue perihperhy, need to be deployed per aggregation vault @@ -58,14 +58,14 @@ contract FourSixTwoSixAggFactory { address feeModuleAddr = Clones.clone(feeModuleImpl); address allocationpointsModuleAddr = Clones.clone(allocationpointsModuleImpl); - // cloning peripheries + // cloning plugins WithdrawalQueue withdrawalQueue = WithdrawalQueue(Clones.clone(withdrawalQueueAddr)); // deploy new aggregation vault - FourSixTwoSixAgg fourSixTwoSixAgg = - new FourSixTwoSixAgg(rewardsModuleAddr, hooksModuleAddr, feeModuleAddr, allocationpointsModuleAddr); + AggregationLayerVault aggregationLayerVault = + new AggregationLayerVault(rewardsModuleAddr, hooksModuleAddr, feeModuleAddr, allocationpointsModuleAddr); - FourSixTwoSixAgg.InitParams memory aggregationVaultInitParams = FourSixTwoSixAgg.InitParams({ + AggregationLayerVault.InitParams memory aggregationVaultInitParams = AggregationLayerVault.InitParams({ evc: evc, balanceTracker: balanceTracker, withdrawalQueuePeriphery: address(withdrawalQueue), @@ -77,9 +77,9 @@ contract FourSixTwoSixAggFactory { initialCashAllocationPoints: _initialCashAllocationPoints }); - withdrawalQueue.init(msg.sender, address(fourSixTwoSixAgg)); - fourSixTwoSixAgg.init(aggregationVaultInitParams); + withdrawalQueue.init(msg.sender, address(aggregationLayerVault)); + aggregationLayerVault.init(aggregationVaultInitParams); - return address(fourSixTwoSixAgg); + return address(aggregationLayerVault); } } diff --git a/src/WithdrawalQueue.sol b/src/WithdrawalQueue.sol index 5c67d253..9a409ef8 100644 --- a/src/WithdrawalQueue.sol +++ b/src/WithdrawalQueue.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -// external dep -import {AccessControlEnumerableUpgradeable} from - "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; +// interfaces import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; // internal dep import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; +// contracts +import {AccessControlEnumerableUpgradeable} from + "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; contract WithdrawalQueue is AccessControlEnumerableUpgradeable { error OutOfBounds(); @@ -29,6 +30,9 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { event ReorderWithdrawalQueue(uint8 index1, uint8 index2); + /// @notice Initialize WithdrawalQueue. + /// @param _owner Aggregation layer vault owner. + /// @param _eulerAggregationVault Address of aggregation layer vault. function init(address _owner, address _eulerAggregationVault) external initializer { WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); $.eulerAggregationVault = _eulerAggregationVault; @@ -38,6 +42,9 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { _setRoleAdmin(WITHDRAW_QUEUE_MANAGER, WITHDRAW_QUEUE_MANAGER_ADMIN); } + /// @notice Add a strategy to withdrawal queue array. + /// @dev Can only be called by the aggregation layer vault's address. + /// @param _strategy Strategy address to add function addStrategyToWithdrawalQueue(address _strategy) external { _isCallerAggregationVault(); @@ -46,6 +53,9 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { $.withdrawalQueue.push(_strategy); } + /// @notice Remove a strategy from withdrawal queue array. + /// @dev Can only be called by the aggregation layer vault's address. + /// @param _strategy Strategy address to add. function removeStrategyFromWithdrawalQueue(address _strategy) external { _isCallerAggregationVault(); @@ -65,9 +75,9 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { } /// @notice Swap two strategies indexes in the withdrawal queue. - /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER. - /// @param _index1 index of first strategy - /// @param _index2 index of second strategy + /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER role. + /// @param _index1 index of first strategy. + /// @param _index2 index of second strategy. function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external onlyRole(WITHDRAW_QUEUE_MANAGER) { WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); @@ -86,6 +96,14 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { emit ReorderWithdrawalQueue(_index1, _index2); } + /// @notice Execute the withdraw initiated in the aggregation layer vault. + /// @dev Can only be called by the aggregation layer vault's address. + /// @param caller Initiator's address of withdraw. + /// @param receiver Withdraw receiver address. + /// @param owner Shares's owner to burn. + /// @param assets Amount of asset to withdraw. + /// @param shares Amount of shares to burn. + /// @param availableAssets Amount of available asset in aggregation layer vault's cash reserve. function callWithdrawalQueue( address caller, address receiver, @@ -121,6 +139,9 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { break; } } + + // re-calculate shares in case of socialized loss + shares = IERC4626(eulerAggregationVaultCached).previewWithdraw(assets); } if (availableAssets < assets) { @@ -132,6 +153,9 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { ); } + /// @notice Get strategy address from withdrawal queue by index. + /// @param _index Index to fetch. + /// @return address Strategy address. function getWithdrawalQueueAtIndex(uint256 _index) external view returns (address) { WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); @@ -139,19 +163,22 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable { } /// @notice Return the withdrawal queue length. - /// @return uint256 length + /// @return uint256 length. function withdrawalQueueLength() external pure returns (uint256) { WithdrawalQueueStorage memory $ = _getWithdrawalQueueStorage(); return $.withdrawalQueue.length; } + /// @dev Check if the msg.sender is the aggregation layer vault. function _isCallerAggregationVault() private view { WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); if (msg.sender != $.eulerAggregationVault) revert NotAuthorized(); } + /// @dev Return storage pointer. + /// @return $ WithdrawalQueueStorage storage struct. function _getWithdrawalQueueStorage() private pure returns (WithdrawalQueueStorage storage $) { assembly { $.slot := WithdrawalQueueStorageLocation diff --git a/src/lib/EventsLib.sol b/src/lib/EventsLib.sol index d8c9e8a2..952b8595 100644 --- a/src/lib/EventsLib.sol +++ b/src/lib/EventsLib.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; library EventsLib { - /// @dev FourSixTwoSixAgg events + /// @dev AggregationLayerVault events event Gulp(uint256 interestLeft, uint256 interestSmearEnd); event Harvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); event AccruePerformanceFee(address indexed feeRecipient, uint256 yield, uint256 feeAssets); diff --git a/src/lib/StorageLib.sol b/src/lib/StorageLib.sol index 33435110..cab1b385 100644 --- a/src/lib/StorageLib.sol +++ b/src/lib/StorageLib.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; struct AggregationVaultStorage { /// @dev EVC address address evc; - /// @dev Total amount of _asset deposited into FourSixTwoSixAgg contract + /// @dev Total amount of _asset deposited into AggregationLayerVault contract uint256 totalAssetsDeposited; /// @dev Total amount of _asset deposited across all strategies. uint256 totalAllocated; diff --git a/test/common/FourSixTwoSixAggBase.t.sol b/test/common/AggregationLayerVaultBase.t.sol similarity index 53% rename from test/common/FourSixTwoSixAggBase.t.sol rename to test/common/AggregationLayerVaultBase.t.sol index acb583e7..988011ec 100644 --- a/test/common/FourSixTwoSixAggBase.t.sol +++ b/test/common/AggregationLayerVaultBase.t.sol @@ -2,19 +2,19 @@ pragma solidity ^0.8.0; import "evk/test/unit/evault/EVaultTestBase.t.sol"; -import {FourSixTwoSixAgg, Strategy} from "../../src/FourSixTwoSixAgg.sol"; +import {AggregationLayerVault, Strategy} from "../../src/AggregationLayerVault.sol"; import {Rebalancer} from "../../src/Rebalancer.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; import {Hooks, HooksModule} from "../../src/modules/Hooks.sol"; import {Rewards} from "../../src/modules/Rewards.sol"; import {Fee} from "../../src/modules/Fee.sol"; -import {FourSixTwoSixAggFactory} from "../../src/FourSixTwoSixAggFactory.sol"; +import {AggregationLayerVaultFactory} from "../../src/AggregationLayerVaultFactory.sol"; import {WithdrawalQueue} from "../../src/WithdrawalQueue.sol"; import {IWithdrawalQueue} from "../../src/interface/IWithdrawalQueue.sol"; import {ErrorsLib} from "../../src/lib/ErrorsLib.sol"; import {AllocationPoints} from "../../src/modules/AllocationPoints.sol"; -contract FourSixTwoSixAggBase is EVaultTestBase { +contract AggregationLayerVaultBase is EVaultTestBase { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; address deployer; @@ -27,13 +27,12 @@ contract FourSixTwoSixAggBase is EVaultTestBase { Hooks hooksImpl; Fee feeModuleImpl; AllocationPoints allocationPointsModuleImpl; - // peripheries + // plugins Rebalancer rebalancer; WithdrawalQueue withdrawalQueueImpl; - FourSixTwoSixAggFactory fourSixTwoSixAggFactory; - - FourSixTwoSixAgg fourSixTwoSixAgg; + AggregationLayerVaultFactory aggregationLayerVaultFactory; + AggregationLayerVault aggregationLayerVault; WithdrawalQueue withdrawalQueue; function setUp() public virtual override { @@ -51,7 +50,7 @@ contract FourSixTwoSixAggBase is EVaultTestBase { rebalancer = new Rebalancer(); withdrawalQueueImpl = new WithdrawalQueue(); - fourSixTwoSixAggFactory = new FourSixTwoSixAggFactory( + aggregationLayerVaultFactory = new AggregationLayerVaultFactory( address(evc), address(0), address(rewardsImpl), @@ -62,74 +61,75 @@ contract FourSixTwoSixAggBase is EVaultTestBase { address(withdrawalQueueImpl) ); - fourSixTwoSixAgg = FourSixTwoSixAgg( - fourSixTwoSixAggFactory.deployEulerAggregationLayer( + aggregationLayerVault = AggregationLayerVault( + aggregationLayerVaultFactory.deployEulerAggregationLayer( address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS ) ); - withdrawalQueue = WithdrawalQueue(fourSixTwoSixAgg.withdrawalQueue()); + withdrawalQueue = WithdrawalQueue(aggregationLayerVault.withdrawalQueue()); // grant admin roles to deployer - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATIONS_MANAGER_ADMIN(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.REBALANCER_ADMIN(), deployer); + aggregationLayerVault.grantRole(aggregationLayerVault.ALLOCATIONS_MANAGER_ADMIN(), deployer); + aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_ADDER_ADMIN(), deployer); + aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_REMOVER_ADMIN(), deployer); + aggregationLayerVault.grantRole(aggregationLayerVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); + aggregationLayerVault.grantRole(aggregationLayerVault.REBALANCER_ADMIN(), deployer); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATIONS_MANAGER(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER(), manager); + aggregationLayerVault.grantRole(aggregationLayerVault.ALLOCATIONS_MANAGER(), manager); + aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_ADDER(), manager); + aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_REMOVER(), manager); + aggregationLayerVault.grantRole(aggregationLayerVault.AGGREGATION_VAULT_MANAGER(), manager); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); vm.stopPrank(); } function testInitialParams() public { - Strategy memory cashReserve = fourSixTwoSixAgg.getStrategy(address(0)); + Strategy memory cashReserve = aggregationLayerVault.getStrategy(address(0)); assertEq(cashReserve.allocated, 0); assertEq(cashReserve.allocationPoints, CASH_RESERVE_ALLOCATION_POINTS); assertEq(cashReserve.active, true); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.ALLOCATIONS_MANAGER()), - fourSixTwoSixAgg.ALLOCATIONS_MANAGER_ADMIN() + aggregationLayerVault.getRoleAdmin(aggregationLayerVault.ALLOCATIONS_MANAGER()), + aggregationLayerVault.ALLOCATIONS_MANAGER_ADMIN() ); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_ADDER()), fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN() + aggregationLayerVault.getRoleAdmin(aggregationLayerVault.STRATEGY_ADDER()), + aggregationLayerVault.STRATEGY_ADDER_ADMIN() ); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.STRATEGY_REMOVER()), - fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN() + aggregationLayerVault.getRoleAdmin(aggregationLayerVault.STRATEGY_REMOVER()), + aggregationLayerVault.STRATEGY_REMOVER_ADMIN() ); assertEq( - fourSixTwoSixAgg.getRoleAdmin(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER()), - fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER_ADMIN() + aggregationLayerVault.getRoleAdmin(aggregationLayerVault.AGGREGATION_VAULT_MANAGER()), + aggregationLayerVault.AGGREGATION_VAULT_MANAGER_ADMIN() ); assertEq( withdrawalQueue.getRoleAdmin(withdrawalQueue.WITHDRAW_QUEUE_MANAGER()), withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN() ); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATIONS_MANAGER_ADMIN(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer)); + assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.ALLOCATIONS_MANAGER_ADMIN(), deployer)); + assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.STRATEGY_ADDER_ADMIN(), deployer)); + assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.STRATEGY_REMOVER_ADMIN(), deployer)); + assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.ALLOCATIONS_MANAGER(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager)); - assertTrue(fourSixTwoSixAgg.hasRole(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER(), manager)); + assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.ALLOCATIONS_MANAGER(), manager)); + assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.STRATEGY_ADDER(), manager)); + assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.STRATEGY_REMOVER(), manager)); + assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.AGGREGATION_VAULT_MANAGER(), manager)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); } function _addStrategy(address from, address strategy, uint256 allocationPoints) internal { vm.prank(from); - fourSixTwoSixAgg.addStrategy(strategy, allocationPoints); + aggregationLayerVault.addStrategy(strategy, allocationPoints); } function _getWithdrawalQueueLength() internal view returns (uint256) { diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index e2c5c8da..3b6ea6fc 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -2,19 +2,19 @@ pragma solidity ^0.8.0; import { - FourSixTwoSixAggBase, - FourSixTwoSixAgg, + AggregationLayerVaultBase, + AggregationLayerVault, console2, EVault, IEVault, IRMTestDefault, TestERC20, - FourSixTwoSixAggFactory, + AggregationLayerVaultFactory, Rewards -} from "../common/FourSixTwoSixAggBase.t.sol"; +} from "../common/AggregationLayerVaultBase.t.sol"; import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol"; -contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { +contract BalanceForwarderE2ETest is AggregationLayerVaultBase { uint256 user1InitialBalance = 100000e18; address trackingReward; @@ -25,7 +25,7 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { vm.startPrank(deployer); trackingReward = address(new TrackingRewardStreams(address(evc), 2 weeks)); - fourSixTwoSixAggFactory = new FourSixTwoSixAggFactory( + aggregationLayerVaultFactory = new AggregationLayerVaultFactory( address(evc), trackingReward, address(rewardsImpl), @@ -35,25 +35,25 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { address(rebalancer), address(withdrawalQueueImpl) ); - fourSixTwoSixAgg = FourSixTwoSixAgg( - fourSixTwoSixAggFactory.deployEulerAggregationLayer( + aggregationLayerVault = AggregationLayerVault( + aggregationLayerVaultFactory.deployEulerAggregationLayer( address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS ) ); // grant admin roles to deployer - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATIONS_MANAGER_ADMIN(), deployer); - // fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER_ADMIN(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER_ADMIN(), deployer); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); + aggregationLayerVault.grantRole(aggregationLayerVault.ALLOCATIONS_MANAGER_ADMIN(), deployer); + // aggregationLayerVault.grantRole(aggregationLayerVault.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); + aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_ADDER_ADMIN(), deployer); + aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_REMOVER_ADMIN(), deployer); + aggregationLayerVault.grantRole(aggregationLayerVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); // grant roles to manager - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.ALLOCATIONS_MANAGER(), manager); - // fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.WITHDRAW_QUEUE_MANAGER(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_ADDER(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.STRATEGY_REMOVER(), manager); - fourSixTwoSixAgg.grantRole(fourSixTwoSixAgg.AGGREGATION_VAULT_MANAGER(), manager); + aggregationLayerVault.grantRole(aggregationLayerVault.ALLOCATIONS_MANAGER(), manager); + // aggregationLayerVault.grantRole(aggregationLayerVault.WITHDRAW_QUEUE_MANAGER(), manager); + aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_ADDER(), manager); + aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_REMOVER(), manager); + aggregationLayerVault.grantRole(aggregationLayerVault.AGGREGATION_VAULT_MANAGER(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; @@ -63,125 +63,125 @@ contract BalanceForwarderE2ETest is FourSixTwoSixAggBase { // deposit into aggregator uint256 amountToDeposit = 10000e18; { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } } function testBalanceForwarderrAddress_Integrity() public view { - assertEq(fourSixTwoSixAgg.balanceTrackerAddress(), trackingReward); + assertEq(aggregationLayerVault.balanceTrackerAddress(), trackingReward); } function testEnableBalanceForwarder() public { vm.prank(user1); - fourSixTwoSixAgg.enableBalanceForwarder(); + aggregationLayerVault.enableBalanceForwarder(); - assertTrue(fourSixTwoSixAgg.balanceForwarderEnabled(user1)); + assertTrue(aggregationLayerVault.balanceForwarderEnabled(user1)); assertEq( - TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), - fourSixTwoSixAgg.balanceOf(user1) + TrackingRewardStreams(trackingReward).balanceOf(user1, address(aggregationLayerVault)), + aggregationLayerVault.balanceOf(user1) ); } function testDisableBalanceForwarder() public { vm.prank(user1); - fourSixTwoSixAgg.enableBalanceForwarder(); + aggregationLayerVault.enableBalanceForwarder(); - assertTrue(fourSixTwoSixAgg.balanceForwarderEnabled(user1)); + assertTrue(aggregationLayerVault.balanceForwarderEnabled(user1)); vm.prank(user1); - fourSixTwoSixAgg.disableBalanceForwarder(); + aggregationLayerVault.disableBalanceForwarder(); - assertFalse(fourSixTwoSixAgg.balanceForwarderEnabled(user1)); - assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), 0); + assertFalse(aggregationLayerVault.balanceForwarderEnabled(user1)); + assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(aggregationLayerVault)), 0); } function testHookWhenReceiverEnabled() public { vm.prank(user1); - fourSixTwoSixAgg.enableBalanceForwarder(); + aggregationLayerVault.enableBalanceForwarder(); // deposit into aggregator uint256 amountToDeposit = 10000e18; { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); assertEq( - TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), - fourSixTwoSixAgg.balanceOf(user1) + TrackingRewardStreams(trackingReward).balanceOf(user1, address(aggregationLayerVault)), + aggregationLayerVault.balanceOf(user1) ); } } function testHookWhenSenderEnabled() public { vm.prank(user1); - fourSixTwoSixAgg.enableBalanceForwarder(); + aggregationLayerVault.enableBalanceForwarder(); // deposit into aggregator uint256 amountToDeposit = 10000e18; { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); assertEq( - TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), - fourSixTwoSixAgg.balanceOf(user1) + TrackingRewardStreams(trackingReward).balanceOf(user1, address(aggregationLayerVault)), + aggregationLayerVault.balanceOf(user1) ); } { - uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + aggregationLayerVault.redeem(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(aggregationLayerVault)), 0); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq( assetTST.balanceOf(user1), - user1AssetTSTBalanceBefore + fourSixTwoSixAgg.convertToAssets(amountToWithdraw) + user1AssetTSTBalanceBefore + aggregationLayerVault.convertToAssets(amountToWithdraw) ); - assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(fourSixTwoSixAgg)), 0); + assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(aggregationLayerVault)), 0); } } } diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 4038be6b..96f902fa 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import { - FourSixTwoSixAggBase, - FourSixTwoSixAgg, + AggregationLayerVaultBase, + AggregationLayerVault, console2, EVault, IEVault, @@ -11,9 +11,9 @@ import { TestERC20, WithdrawalQueue, Strategy -} from "../common/FourSixTwoSixAggBase.t.sol"; +} from "../common/AggregationLayerVaultBase.t.sol"; -contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { +contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -30,41 +30,42 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); assertEq( - (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash + (aggregationLayerVault.getStrategy(address(eTST))).allocated, + strategyBefore.allocated + expectedStrategyCash ); } @@ -72,37 +73,37 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // partial withdraw, no need to withdraw from strategy as cash reserve is enough uint256 amountToWithdraw = 6000e18; { - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); - uint256 strategyShareBalanceBefore = eTST.balanceOf(address(fourSixTwoSixAgg)); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + uint256 strategyShareBalanceBefore = eTST.balanceOf(address(aggregationLayerVault)); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - fourSixTwoSixAgg.withdraw(amountToWithdraw, user1, user1); + aggregationLayerVault.withdraw(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), strategyShareBalanceBefore); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(aggregationLayerVault)), strategyShareBalanceBefore); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated); } // full withdraw, will have to withdraw from strategy as cash reserve is not enough { amountToWithdraw = amountToDeposit - amountToWithdraw; - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - fourSixTwoSixAgg.withdraw(amountToWithdraw, user1, user1); + aggregationLayerVault.withdraw(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(aggregationLayerVault)), 0); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, 0); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, 0); } } @@ -111,67 +112,67 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); } vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + eTST.skim(type(uint256).max, address(aggregationLayerVault)); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + aggregationLayerVault.redeem(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), yield); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(aggregationLayerVault)), yield); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq( assetTST.balanceOf(user1), - user1AssetTSTBalanceBefore + fourSixTwoSixAgg.convertToAssets(amountToWithdraw) + user1AssetTSTBalanceBefore + aggregationLayerVault.convertToAssets(amountToWithdraw) ); } } @@ -196,19 +197,19 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -217,19 +218,19 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); - Strategy memory eTSTsecondarystrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); + Strategy memory eTSTstrategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + Strategy memory eTSTsecondarystrategyBefore = aggregationLayerVault.getStrategy(address(eTSTsecondary)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTstrategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), eTSTstrategyBefore.allocated); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), eTSTsecondarystrategyBefore.allocated ); - uint256 expectedeTSTStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() - * eTSTstrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); - uint256 expectedeTSTsecondaryStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() - * eTSTsecondarystrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedeTSTStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); assertTrue(expectedeTSTStrategyCash != 0); assertTrue(expectedeTSTsecondaryStrategyCash != 0); @@ -238,29 +239,31 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + aggregationLayerVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash + ); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedeTSTStrategyCash); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), expectedeTSTsecondaryStrategyCash ); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); assertEq( - (fourSixTwoSixAgg.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + (aggregationLayerVault.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash ); assertEq( - assetTST.balanceOf(address(fourSixTwoSixAgg)), + assetTST.balanceOf(address(aggregationLayerVault)), amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) ); } vm.warp(block.timestamp + 86400); // mock an increase of aggregator balance due to yield - uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); - uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance); uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; uint256 aggrNeweTSTsecondaryUnderlyingBalance = aggrCurrenteTSTsecondaryUnderlyingBalance * 11e17 / 1e18; @@ -269,25 +272,25 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { assetTST.mint(address(eTST), eTSTYield); assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield); - eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); - eTSTsecondary.skim(type(uint256).max, address(fourSixTwoSixAgg)); + eTST.skim(type(uint256).max, address(aggregationLayerVault)); + eTSTsecondary.skim(type(uint256).max, address(aggregationLayerVault)); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + aggregationLayerVault.redeem(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(fourSixTwoSixAgg)), 0); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(aggregationLayerVault)), 0); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq( assetTST.balanceOf(user1), - user1AssetTSTBalanceBefore + fourSixTwoSixAgg.convertToAssets(amountToWithdraw) + user1AssetTSTBalanceBefore + aggregationLayerVault.convertToAssets(amountToWithdraw) ); } } @@ -297,70 +300,72 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); } vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + eTST.skim(type(uint256).max, address(aggregationLayerVault)); // harvest vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); + aggregationLayerVault.harvest(address(eTST)); vm.warp(block.timestamp + 2 weeks); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + aggregationLayerVault.redeem(amountToWithdraw, user1, user1); // all yield is distributed - assertApproxEqAbs(eTST.balanceOf(address(fourSixTwoSixAgg)), 0, 1); - assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1); - assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertApproxEqAbs(eTST.balanceOf(address(aggregationLayerVault)), 0, 1); + assertApproxEqAbs( + aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1 + ); + assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToDeposit + yield, 1); } } @@ -385,19 +390,19 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -406,19 +411,19 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); - Strategy memory eTSTsecondarystrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); + Strategy memory eTSTstrategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + Strategy memory eTSTsecondarystrategyBefore = aggregationLayerVault.getStrategy(address(eTSTsecondary)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTstrategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), eTSTstrategyBefore.allocated); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), eTSTsecondarystrategyBefore.allocated ); - uint256 expectedeTSTStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() - * eTSTstrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); - uint256 expectedeTSTsecondaryStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() - * eTSTsecondarystrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedeTSTStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); assertTrue(expectedeTSTStrategyCash != 0); assertTrue(expectedeTSTsecondaryStrategyCash != 0); @@ -427,20 +432,22 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + aggregationLayerVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash + ); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedeTSTStrategyCash); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), expectedeTSTsecondaryStrategyCash ); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); assertEq( - (fourSixTwoSixAgg.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + (aggregationLayerVault.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash ); assertEq( - assetTST.balanceOf(address(fourSixTwoSixAgg)), + assetTST.balanceOf(address(aggregationLayerVault)), amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) ); } @@ -450,9 +457,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { uint256 eTSTsecondaryYield; { // mock an increase of aggregator balance due to yield - uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); - uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance); uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; @@ -462,8 +469,8 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { assetTST.mint(address(eTST), eTSTYield); assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield); - eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); - eTSTsecondary.skim(type(uint256).max, address(fourSixTwoSixAgg)); + eTST.skim(type(uint256).max, address(aggregationLayerVault)); + eTSTsecondary.skim(type(uint256).max, address(aggregationLayerVault)); } // harvest @@ -471,22 +478,24 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToHarvest[0] = address(eTST); strategiesToHarvest[1] = address(eTSTsecondary); vm.prank(user1); - fourSixTwoSixAgg.harvestMultipleStrategies(strategiesToHarvest); + aggregationLayerVault.harvestMultipleStrategies(strategiesToHarvest); vm.warp(block.timestamp + 2 weeks); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + aggregationLayerVault.redeem(amountToWithdraw, user1, user1); - assertApproxEqAbs(eTST.balanceOf(address(fourSixTwoSixAgg)), 0, 0); - assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1); - assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertApproxEqAbs(eTST.balanceOf(address(aggregationLayerVault)), 0, 0); + assertApproxEqAbs( + aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1 + ); + assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertApproxEqAbs( assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToDeposit + eTSTYield + eTSTsecondaryYield, @@ -515,19 +524,19 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -536,19 +545,19 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - Strategy memory eTSTstrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); - Strategy memory eTSTsecondarystrategyBefore = fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); + Strategy memory eTSTstrategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + Strategy memory eTSTsecondarystrategyBefore = aggregationLayerVault.getStrategy(address(eTSTsecondary)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTstrategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), eTSTstrategyBefore.allocated); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), eTSTsecondarystrategyBefore.allocated ); - uint256 expectedeTSTStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() - * eTSTstrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); - uint256 expectedeTSTsecondaryStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() - * eTSTsecondarystrategyBefore.allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedeTSTStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); assertTrue(expectedeTSTStrategyCash != 0); assertTrue(expectedeTSTsecondaryStrategyCash != 0); @@ -557,20 +566,22 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedeTSTStrategyCash); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + aggregationLayerVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash + ); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedeTSTStrategyCash); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), expectedeTSTsecondaryStrategyCash ); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); assertEq( - (fourSixTwoSixAgg.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + (aggregationLayerVault.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash ); assertEq( - assetTST.balanceOf(address(fourSixTwoSixAgg)), + assetTST.balanceOf(address(aggregationLayerVault)), amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) ); } @@ -580,9 +591,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { uint256 eTSTsecondaryYield; { // mock an increase of aggregator balance due to yield - uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); - uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance); uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; @@ -592,26 +603,26 @@ contract DepositRebalanceHarvestWithdrawE2ETest is FourSixTwoSixAggBase { assetTST.mint(address(eTST), eTSTYield); assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield); - eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); - eTSTsecondary.skim(type(uint256).max, address(fourSixTwoSixAgg)); + eTST.skim(type(uint256).max, address(aggregationLayerVault)); + eTSTsecondary.skim(type(uint256).max, address(aggregationLayerVault)); } // harvest address[] memory strategiesToHarvest = new address[](1); strategiesToHarvest[0] = address(eTST); vm.prank(user1); - fourSixTwoSixAgg.harvestMultipleStrategies(strategiesToHarvest); + aggregationLayerVault.harvestMultipleStrategies(strategiesToHarvest); vm.warp(block.timestamp + 2 weeks); vm.prank(manager); - fourSixTwoSixAgg.removeStrategy(address(eTSTsecondary)); + aggregationLayerVault.removeStrategy(address(eTSTsecondary)); { - uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); + uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); vm.prank(user1); vm.expectRevert(WithdrawalQueue.NotEnoughAssets.selector); - fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + aggregationLayerVault.redeem(amountToWithdraw, user1, user1); } } } diff --git a/test/e2e/HooksE2ETest.t.sol b/test/e2e/HooksE2ETest.t.sol index a7570b48..59bb388b 100644 --- a/test/e2e/HooksE2ETest.t.sol +++ b/test/e2e/HooksE2ETest.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import { - FourSixTwoSixAggBase, - FourSixTwoSixAgg, + AggregationLayerVaultBase, + AggregationLayerVault, console2, EVault, IEVault, @@ -11,9 +11,9 @@ import { TestERC20, IHookTarget, ErrorsLib -} from "../common/FourSixTwoSixAggBase.t.sol"; +} from "../common/AggregationLayerVaultBase.t.sol"; -contract HooksE2ETest is FourSixTwoSixAggBase { +contract HooksE2ETest is AggregationLayerVaultBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -26,38 +26,38 @@ contract HooksE2ETest is FourSixTwoSixAggBase { } function testSetHooksConfig() public { - uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT() | fourSixTwoSixAgg.WITHDRAW() - | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); + uint32 expectedHookedFns = aggregationLayerVault.DEPOSIT() | aggregationLayerVault.WITHDRAW() + | aggregationLayerVault.ADD_STRATEGY() | aggregationLayerVault.REMOVE_STRATEGY(); vm.startPrank(manager); address hooksContract = address(new HooksContract()); - fourSixTwoSixAgg.setHooksConfig(hooksContract, expectedHookedFns); + aggregationLayerVault.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); - (address hookTarget, uint32 hookedFns) = fourSixTwoSixAgg.getHooksConfig(); + (address hookTarget, uint32 hookedFns) = aggregationLayerVault.getHooksConfig(); assertEq(hookTarget, hooksContract); assertEq(hookedFns, expectedHookedFns); } function testSetHooksConfigWithAddressZero() public { - uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT() | fourSixTwoSixAgg.WITHDRAW() - | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); + uint32 expectedHookedFns = aggregationLayerVault.DEPOSIT() | aggregationLayerVault.WITHDRAW() + | aggregationLayerVault.ADD_STRATEGY() | aggregationLayerVault.REMOVE_STRATEGY(); vm.startPrank(manager); vm.expectRevert(ErrorsLib.InvalidHooksTarget.selector); - fourSixTwoSixAgg.setHooksConfig(address(0), expectedHookedFns); + aggregationLayerVault.setHooksConfig(address(0), expectedHookedFns); vm.stopPrank(); } function testSetHooksConfigWithNotHooksContract() public { - uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT() | fourSixTwoSixAgg.WITHDRAW() - | fourSixTwoSixAgg.ADD_STRATEGY() | fourSixTwoSixAgg.REMOVE_STRATEGY(); + uint32 expectedHookedFns = aggregationLayerVault.DEPOSIT() | aggregationLayerVault.WITHDRAW() + | aggregationLayerVault.ADD_STRATEGY() | aggregationLayerVault.REMOVE_STRATEGY(); vm.startPrank(manager); address hooksContract = address(new NotHooksContract()); vm.expectRevert(ErrorsLib.NotHooksContract.selector); - fourSixTwoSixAgg.setHooksConfig(hooksContract, expectedHookedFns); + aggregationLayerVault.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); } @@ -66,33 +66,33 @@ contract HooksE2ETest is FourSixTwoSixAggBase { vm.startPrank(manager); address hooksContract = address(new HooksContract()); vm.expectRevert(ErrorsLib.InvalidHookedFns.selector); - fourSixTwoSixAgg.setHooksConfig(hooksContract, expectedHookedFns); + aggregationLayerVault.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); } function testHookedDeposit() public { - uint32 expectedHookedFns = fourSixTwoSixAgg.DEPOSIT(); + uint32 expectedHookedFns = aggregationLayerVault.DEPOSIT(); vm.startPrank(manager); address hooksContract = address(new HooksContract()); - fourSixTwoSixAgg.setHooksConfig(hooksContract, expectedHookedFns); + aggregationLayerVault.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); uint256 amountToDeposit = 10000e18; // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } } diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 5b3a811b..1bb5cb55 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -2,17 +2,17 @@ pragma solidity ^0.8.0; import { - FourSixTwoSixAggBase, - FourSixTwoSixAgg, + AggregationLayerVaultBase, + AggregationLayerVault, console2, EVault, IEVault, IRMTestDefault, TestERC20, Strategy -} from "../common/FourSixTwoSixAggBase.t.sol"; +} from "../common/AggregationLayerVaultBase.t.sol"; -contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { +contract PerformanceFeeE2ETest is AggregationLayerVaultBase { uint256 user1InitialBalance = 100000e18; address feeRecipient; @@ -30,18 +30,18 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { function testSetPerformanceFee() public { { - (, uint256 fee) = fourSixTwoSixAgg.performanceFeeConfig(); + (, uint256 fee) = aggregationLayerVault.performanceFeeConfig(); assertEq(fee, 0); } uint256 newPerformanceFee = 3e17; vm.startPrank(manager); - fourSixTwoSixAgg.setFeeRecipient(feeRecipient); - fourSixTwoSixAgg.setPerformanceFee(newPerformanceFee); + aggregationLayerVault.setFeeRecipient(feeRecipient); + aggregationLayerVault.setPerformanceFee(newPerformanceFee); vm.stopPrank(); - (address feeRecipientAddr, uint256 fee) = fourSixTwoSixAgg.performanceFeeConfig(); + (address feeRecipientAddr, uint256 fee) = aggregationLayerVault.performanceFeeConfig(); assertEq(fee, newPerformanceFee); assertEq(feeRecipientAddr, feeRecipient); } @@ -50,107 +50,111 @@ contract PerformanceFeeE2ETest is FourSixTwoSixAggBase { uint256 newPerformanceFee = 3e17; vm.startPrank(manager); - fourSixTwoSixAgg.setFeeRecipient(feeRecipient); - fourSixTwoSixAgg.setPerformanceFee(newPerformanceFee); + aggregationLayerVault.setFeeRecipient(feeRecipient); + aggregationLayerVault.setPerformanceFee(newPerformanceFee); vm.stopPrank(); uint256 amountToDeposit = 10000e18; // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); } vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% uint256 yield; { - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + eTST.skim(type(uint256).max, address(aggregationLayerVault)); } - (, uint256 performanceFee) = fourSixTwoSixAgg.performanceFeeConfig(); + (, uint256 performanceFee) = aggregationLayerVault.performanceFeeConfig(); uint256 expectedPerformanceFee = yield * performanceFee / 1e18; - Strategy memory strategyBeforeHarvest = fourSixTwoSixAgg.getStrategy(address(eTST)); - uint256 totalAllocatedBefore = fourSixTwoSixAgg.totalAllocated(); + Strategy memory strategyBeforeHarvest = aggregationLayerVault.getStrategy(address(eTST)); + uint256 totalAllocatedBefore = aggregationLayerVault.totalAllocated(); // harvest vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); + aggregationLayerVault.harvest(address(eTST)); assertEq(assetTST.balanceOf(feeRecipient), expectedPerformanceFee); assertEq( - fourSixTwoSixAgg.getStrategy(address(eTST)).allocated, + aggregationLayerVault.getStrategy(address(eTST)).allocated, strategyBeforeHarvest.allocated + yield - expectedPerformanceFee ); - assertEq(fourSixTwoSixAgg.totalAllocated(), totalAllocatedBefore + yield - expectedPerformanceFee); + assertEq(aggregationLayerVault.totalAllocated(), totalAllocatedBefore + yield - expectedPerformanceFee); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = fourSixTwoSixAgg.totalSupply(); + uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); - uint256 expectedAssetTST = fourSixTwoSixAgg.convertToAssets(fourSixTwoSixAgg.balanceOf(user1)); + uint256 expectedAssetTST = aggregationLayerVault.convertToAssets(aggregationLayerVault.balanceOf(user1)); vm.prank(user1); - fourSixTwoSixAgg.redeem(amountToWithdraw, user1, user1); + aggregationLayerVault.redeem(amountToWithdraw, user1, user1); - assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssetTST, 1); - assertEq(fourSixTwoSixAgg.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertApproxEqAbs( + aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssetTST, 1 + ); + assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedAssetTST, 1); } // full withdraw of recipient fees { - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 assetTSTBalanceBefore = assetTST.balanceOf(feeRecipient); - uint256 feeShares = fourSixTwoSixAgg.balanceOf(feeRecipient); - uint256 expectedAssets = fourSixTwoSixAgg.convertToAssets(feeShares); + uint256 feeShares = aggregationLayerVault.balanceOf(feeRecipient); + uint256 expectedAssets = aggregationLayerVault.convertToAssets(feeShares); vm.prank(feeRecipient); - fourSixTwoSixAgg.redeem(feeShares, feeRecipient, feeRecipient); + aggregationLayerVault.redeem(feeShares, feeRecipient, feeRecipient); - assertApproxEqAbs(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssets, 1); - assertEq(fourSixTwoSixAgg.totalSupply(), 0); + assertApproxEqAbs( + aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssets, 1 + ); + assertEq(aggregationLayerVault.totalSupply(), 0); assertApproxEqAbs(assetTST.balanceOf(feeRecipient), assetTSTBalanceBefore + expectedAssets, 1); } } diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index d7373ad4..c5ebcfc6 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import { - FourSixTwoSixAggBase, - FourSixTwoSixAgg, + AggregationLayerVaultBase, + AggregationLayerVault, console2, EVault, IEVault, @@ -11,9 +11,9 @@ import { TestERC20, Strategy, ErrorsLib -} from "../common/FourSixTwoSixAggBase.t.sol"; +} from "../common/AggregationLayerVaultBase.t.sol"; -contract StrategyCapE2ETest is FourSixTwoSixAggBase { +contract StrategyCapE2ETest is AggregationLayerVaultBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -28,12 +28,12 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { function testSetCap() public { uint256 cap = 1000000e18; - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).cap, 0); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).cap, 0); vm.prank(manager); - fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); + aggregationLayerVault.setStrategyCap(address(eTST), cap); - Strategy memory strategy = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategy = aggregationLayerVault.getStrategy(address(eTST)); assertEq(strategy.cap, cap); } @@ -43,53 +43,54 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { vm.prank(manager); vm.expectRevert(ErrorsLib.InactiveStrategy.selector); - fourSixTwoSixAgg.setStrategyCap(address(0x2), cap); + aggregationLayerVault.setStrategyCap(address(0x2), cap); } function testRebalanceAfterHittingCap() public { uint256 cap = 3333333333333333333333; vm.prank(manager); - fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); + aggregationLayerVault.setStrategyCap(address(eTST), cap); uint256 amountToDeposit = 10000e18; // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); assertEq( - (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash + (aggregationLayerVault.getStrategy(address(eTST))).allocated, + strategyBefore.allocated + expectedStrategyCash ); } @@ -97,17 +98,17 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { vm.warp(block.timestamp + 86400); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); - uint256 strategyAllocatedBefore = (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated; + uint256 strategyAllocatedBefore = (aggregationLayerVault.getStrategy(address(eTST))).allocated; address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); vm.stopPrank(); - assertEq(strategyAllocatedBefore, (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated); + assertEq(strategyAllocatedBefore, (aggregationLayerVault.getStrategy(address(eTST))).allocated); } function testRebalanceWhentargetAllocationGreaterThanCap() public { @@ -115,45 +116,45 @@ contract StrategyCapE2ETest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); // set cap 10% less than target allocation uint256 cap = expectedStrategyCash * 9e17 / 1e18; vm.prank(manager); - fourSixTwoSixAgg.setStrategyCap(address(eTST), cap); + aggregationLayerVault.setStrategyCap(address(eTST), cap); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), cap); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), cap); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated + cap); + assertEq(aggregationLayerVault.totalAllocated(), cap); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), cap); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated + cap); } } } diff --git a/test/e2e/StrategyRewardsE2ETest.t.sol b/test/e2e/StrategyRewardsE2ETest.t.sol index edbcb9a9..91303088 100644 --- a/test/e2e/StrategyRewardsE2ETest.t.sol +++ b/test/e2e/StrategyRewardsE2ETest.t.sol @@ -2,17 +2,17 @@ pragma solidity ^0.8.0; import { - FourSixTwoSixAggBase, - FourSixTwoSixAgg, + AggregationLayerVaultBase, + AggregationLayerVault, console2, EVault, IEVault, IRMTestDefault, TestERC20 -} from "../common/FourSixTwoSixAggBase.t.sol"; +} from "../common/AggregationLayerVaultBase.t.sol"; import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol"; -contract StrategyRewardsE2ETest is FourSixTwoSixAggBase { +contract StrategyRewardsE2ETest is AggregationLayerVaultBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -26,19 +26,19 @@ contract StrategyRewardsE2ETest is FourSixTwoSixAggBase { function testOptInStrategyRewards() public { vm.prank(manager); - fourSixTwoSixAgg.optInStrategyRewards(address(eTST)); + aggregationLayerVault.optInStrategyRewards(address(eTST)); - assertTrue(eTST.balanceForwarderEnabled(address(fourSixTwoSixAgg))); + assertTrue(eTST.balanceForwarderEnabled(address(aggregationLayerVault))); } function testOptOutStrategyRewards() public { vm.prank(manager); - fourSixTwoSixAgg.optInStrategyRewards(address(eTST)); - assertTrue(eTST.balanceForwarderEnabled(address(fourSixTwoSixAgg))); + aggregationLayerVault.optInStrategyRewards(address(eTST)); + assertTrue(eTST.balanceForwarderEnabled(address(aggregationLayerVault))); vm.prank(manager); - fourSixTwoSixAgg.optOutStrategyRewards(address(eTST)); + aggregationLayerVault.optOutStrategyRewards(address(eTST)); - assertFalse(eTST.balanceForwarderEnabled(address(fourSixTwoSixAgg))); + assertFalse(eTST.balanceForwarderEnabled(address(aggregationLayerVault))); } } diff --git a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol index 07c38942..ceac1837 100644 --- a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol +++ b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg, Strategy} from "../common/FourSixTwoSixAggBase.t.sol"; +import {AggregationLayerVaultBase, AggregationLayerVault, Strategy} from "../common/AggregationLayerVaultBase.t.sol"; -contract AdjustAllocationsPointsFuzzTest is FourSixTwoSixAggBase { +contract AdjustAllocationsPointsFuzzTest is AggregationLayerVaultBase { function setUp() public virtual override { super.setUp(); @@ -14,23 +14,23 @@ contract AdjustAllocationsPointsFuzzTest is FourSixTwoSixAggBase { function testFuzzAdjustAllocationPoints(uint256 _newAllocationPoints) public { _newAllocationPoints = bound(_newAllocationPoints, 0, type(uint120).max); - uint256 strategyAllocationPoints = (fourSixTwoSixAgg.getStrategy(address(eTST))).allocationPoints; - uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); + uint256 strategyAllocationPoints = (aggregationLayerVault.getStrategy(address(eTST))).allocationPoints; + uint256 totalAllocationPointsBefore = aggregationLayerVault.totalAllocationPoints(); uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); - fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), _newAllocationPoints); + aggregationLayerVault.adjustAllocationPoints(address(eTST), _newAllocationPoints); - Strategy memory strategy = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategy = aggregationLayerVault.getStrategy(address(eTST)); if (_newAllocationPoints < strategyAllocationPoints) { assertEq( - fourSixTwoSixAgg.totalAllocationPoints(), + aggregationLayerVault.totalAllocationPoints(), totalAllocationPointsBefore - (strategyAllocationPoints - _newAllocationPoints) ); } else { assertEq( - fourSixTwoSixAgg.totalAllocationPoints(), + aggregationLayerVault.totalAllocationPoints(), totalAllocationPointsBefore + (_newAllocationPoints - strategyAllocationPoints) ); } diff --git a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol index ae9b9efc..dea9b6df 100644 --- a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol +++ b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {console2, FourSixTwoSixAggBase, FourSixTwoSixAgg} from "../common/FourSixTwoSixAggBase.t.sol"; +import {console2, AggregationLayerVaultBase, AggregationLayerVault} from "../common/AggregationLayerVaultBase.t.sol"; -contract DepositWithdrawMintBurnFuzzTest is FourSixTwoSixAggBase { +contract DepositWithdrawMintBurnFuzzTest is AggregationLayerVaultBase { uint256 constant MAX_ALLOWED = type(uint256).max; function setUp() public virtual override { @@ -14,16 +14,16 @@ contract DepositWithdrawMintBurnFuzzTest is FourSixTwoSixAggBase { // moch the scenario of _assets ownership assetTST.mint(user1, _assets); - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); _deposit(user1, _assets); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + _assets); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + _assets); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + _assets); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + _assets); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + _assets); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + _assets); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - _assets); } @@ -45,36 +45,36 @@ contract DepositWithdrawMintBurnFuzzTest is FourSixTwoSixAggBase { vm.warp(block.timestamp + _timestampAfterDeposit); // fuzz partial & full withdraws - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 receiverAssetBalanceBefore = assetTST.balanceOf(_receiver); vm.startPrank(user1); - fourSixTwoSixAgg.withdraw(_assetsToWithdraw, _receiver, user1); + aggregationLayerVault.withdraw(_assetsToWithdraw, _receiver, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore - _assetsToWithdraw); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore - _assetsToWithdraw); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore - _assetsToWithdraw); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore - _assetsToWithdraw); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + _assetsToWithdraw); } function testFuzzMint(uint256 _shares) public { // moch the scenario of _assets ownership - uint256 assets = fourSixTwoSixAgg.previewMint(_shares); + uint256 assets = aggregationLayerVault.previewMint(_shares); assetTST.mint(user1, assets); - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); _mint(user1, assets, _shares); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + _shares); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + _shares); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + assets); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + _shares); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + _shares); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + assets); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - assets); } @@ -91,38 +91,38 @@ contract DepositWithdrawMintBurnFuzzTest is FourSixTwoSixAggBase { _timestampAfterDeposit = bound(_timestampAfterDeposit, 0, 86400); // deposit - uint256 assetsToDeposit = fourSixTwoSixAgg.previewMint(_sharesToMint); + uint256 assetsToDeposit = aggregationLayerVault.previewMint(_sharesToMint); assetTST.mint(user1, assetsToDeposit); _mint(user1, assetsToDeposit, _sharesToMint); vm.warp(block.timestamp + _timestampAfterDeposit); // fuzz partial & full redeem - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 receiverAssetBalanceBefore = assetTST.balanceOf(_receiver); vm.startPrank(user1); - uint256 assetsToWithdraw = fourSixTwoSixAgg.redeem(_sharesToRedeem, _receiver, user1); + uint256 assetsToWithdraw = aggregationLayerVault.redeem(_sharesToRedeem, _receiver, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore - _sharesToRedeem); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore - _sharesToRedeem); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore - _sharesToRedeem); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore - _sharesToRedeem); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + assetsToWithdraw); } function _deposit(address _from, uint256 _assets) private { vm.startPrank(_from); - assetTST.approve(address(fourSixTwoSixAgg), _assets); - fourSixTwoSixAgg.deposit(_assets, _from); + assetTST.approve(address(aggregationLayerVault), _assets); + aggregationLayerVault.deposit(_assets, _from); vm.stopPrank(); } function _mint(address _from, uint256 _assets, uint256 _shares) private { vm.startPrank(_from); - assetTST.approve(address(fourSixTwoSixAgg), _assets); - fourSixTwoSixAgg.mint(_shares, _from); + assetTST.approve(address(aggregationLayerVault), _assets); + aggregationLayerVault.mint(_shares, _from); vm.stopPrank(); } } diff --git a/test/unit/AddStrategyTest.t.sol b/test/unit/AddStrategyTest.t.sol index 493c13e9..0efc9e8c 100644 --- a/test/unit/AddStrategyTest.t.sol +++ b/test/unit/AddStrategyTest.t.sol @@ -1,22 +1,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg} from "../common/FourSixTwoSixAggBase.t.sol"; +import {AggregationLayerVaultBase, AggregationLayerVault} from "../common/AggregationLayerVaultBase.t.sol"; -contract AddStrategyTest is FourSixTwoSixAggBase { +contract AddStrategyTest is AggregationLayerVaultBase { function setUp() public virtual override { super.setUp(); } function testAddStrategy() public { uint256 allocationPoints = 500e18; - uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = aggregationLayerVault.totalAllocationPoints(); assertEq(_getWithdrawalQueueLength(), 0); _addStrategy(manager, address(eTST), allocationPoints); - assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); + assertEq(aggregationLayerVault.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); assertEq(_getWithdrawalQueueLength(), 1); } @@ -40,13 +40,13 @@ contract AddStrategyTest is FourSixTwoSixAggBase { function testAddStrategy_AlreadyAddedStrategy() public { uint256 allocationPoints = 500e18; - uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = aggregationLayerVault.totalAllocationPoints(); assertEq(_getWithdrawalQueueLength(), 0); _addStrategy(manager, address(eTST), allocationPoints); - assertEq(fourSixTwoSixAgg.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); + assertEq(aggregationLayerVault.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); assertEq(_getWithdrawalQueueLength(), 1); vm.expectRevert(); diff --git a/test/unit/AdjustAllocationPointsTest.t.sol b/test/unit/AdjustAllocationPointsTest.t.sol index 12cf042b..bf440e35 100644 --- a/test/unit/AdjustAllocationPointsTest.t.sol +++ b/test/unit/AdjustAllocationPointsTest.t.sol @@ -1,9 +1,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg, Strategy, ErrorsLib} from "../common/FourSixTwoSixAggBase.t.sol"; - -contract AdjustAllocationsPointsTest is FourSixTwoSixAggBase { +import { + AggregationLayerVaultBase, + AggregationLayerVault, + Strategy, + ErrorsLib +} from "../common/AggregationLayerVaultBase.t.sol"; + +contract AdjustAllocationsPointsTest is AggregationLayerVaultBase { uint256 initialStrategyAllocationPoints = 500e18; function setUp() public virtual override { @@ -14,16 +19,16 @@ contract AdjustAllocationsPointsTest is FourSixTwoSixAggBase { function testAdjustAllocationPoints() public { uint256 newAllocationPoints = 859e18; - uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = aggregationLayerVault.totalAllocationPoints(); uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); - fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), newAllocationPoints); + aggregationLayerVault.adjustAllocationPoints(address(eTST), newAllocationPoints); - Strategy memory strategy = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategy = aggregationLayerVault.getStrategy(address(eTST)); assertEq( - fourSixTwoSixAgg.totalAllocationPoints(), + aggregationLayerVault.totalAllocationPoints(), totalAllocationPointsBefore + (newAllocationPoints - initialStrategyAllocationPoints) ); assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore); @@ -35,7 +40,7 @@ contract AdjustAllocationsPointsTest is FourSixTwoSixAggBase { vm.startPrank(deployer); vm.expectRevert(); - fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), newAllocationPoints); + aggregationLayerVault.adjustAllocationPoints(address(eTST), newAllocationPoints); vm.stopPrank(); } @@ -44,7 +49,7 @@ contract AdjustAllocationsPointsTest is FourSixTwoSixAggBase { vm.startPrank(manager); vm.expectRevert(ErrorsLib.InactiveStrategy.selector); - fourSixTwoSixAgg.adjustAllocationPoints(address(eTST2), newAllocationPoints); + aggregationLayerVault.adjustAllocationPoints(address(eTST2), newAllocationPoints); vm.stopPrank(); } } diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index fca77edb..f1ac33ff 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -2,10 +2,14 @@ pragma solidity ^0.8.0; import { - FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault, Strategy -} from "../common/FourSixTwoSixAggBase.t.sol"; - -contract GulpTest is FourSixTwoSixAggBase { + AggregationLayerVaultBase, + AggregationLayerVault, + console2, + EVault, + Strategy +} from "../common/AggregationLayerVaultBase.t.sol"; + +contract GulpTest is AggregationLayerVaultBase { uint256 user1InitialBalance = 100000e18; uint256 amountToDeposit = 10000e18; @@ -19,140 +23,142 @@ contract GulpTest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); } } function testGulpAfterNegativeYieldEqualToInterestLeft() public { - fourSixTwoSixAgg.gulp(); - FourSixTwoSixAgg.AggregationVaultSavingRate memory ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + aggregationLayerVault.gulp(); + AggregationLayerVault.AggregationVaultSavingRate memory ers = + aggregationLayerVault.getAggregationVaultSavingRate(); + assertEq(aggregationLayerVault.interestAccrued(), 0); assertEq(ers.interestLeft, 0); vm.warp(block.timestamp + 2 days); - fourSixTwoSixAgg.gulp(); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + aggregationLayerVault.gulp(); + assertEq(aggregationLayerVault.interestAccrued(), 0); vm.warp(block.timestamp + 1 days); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + assertEq(aggregationLayerVault.interestAccrued(), 0); uint256 yield; { - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + eTST.skim(type(uint256).max, address(aggregationLayerVault)); } vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); + aggregationLayerVault.harvest(address(eTST)); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + assertEq(aggregationLayerVault.interestAccrued(), 0); vm.warp(block.timestamp + 1 days); // interest per day 23.809523809523 - assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); - fourSixTwoSixAgg.gulp(); - ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); + assertEq(aggregationLayerVault.interestAccrued(), 23809523809523809523); + aggregationLayerVault.gulp(); + ers = aggregationLayerVault.getAggregationVaultSavingRate(); assertEq(ers.interestLeft, yield - 23809523809523809523); // move close to end of smearing vm.warp(block.timestamp + 11 days); - fourSixTwoSixAgg.gulp(); - ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); + aggregationLayerVault.gulp(); + ers = aggregationLayerVault.getAggregationVaultSavingRate(); // mock a decrease of strategy balance by ers.interestLeft - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - ers.interestLeft; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encodeWithSelector(EVault.balanceOf.selector, address(aggregationLayerVault)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); + aggregationLayerVault.harvest(address(eTST)); } function testGulpAfterNegativeYieldBiggerThanInterestLeft() public { - fourSixTwoSixAgg.gulp(); - FourSixTwoSixAgg.AggregationVaultSavingRate memory ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + aggregationLayerVault.gulp(); + AggregationLayerVault.AggregationVaultSavingRate memory ers = + aggregationLayerVault.getAggregationVaultSavingRate(); + assertEq(aggregationLayerVault.interestAccrued(), 0); assertEq(ers.interestLeft, 0); vm.warp(block.timestamp + 2 days); - fourSixTwoSixAgg.gulp(); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + aggregationLayerVault.gulp(); + assertEq(aggregationLayerVault.interestAccrued(), 0); vm.warp(block.timestamp + 1 days); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + assertEq(aggregationLayerVault.interestAccrued(), 0); uint256 yield; { - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(fourSixTwoSixAgg)); + eTST.skim(type(uint256).max, address(aggregationLayerVault)); } vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); + aggregationLayerVault.harvest(address(eTST)); - assertEq(fourSixTwoSixAgg.interestAccrued(), 0); + assertEq(aggregationLayerVault.interestAccrued(), 0); vm.warp(block.timestamp + 1 days); // interest per day 23.809523809523 - assertEq(fourSixTwoSixAgg.interestAccrued(), 23809523809523809523); - fourSixTwoSixAgg.gulp(); - ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); + assertEq(aggregationLayerVault.interestAccrued(), 23809523809523809523); + aggregationLayerVault.gulp(); + ers = aggregationLayerVault.getAggregationVaultSavingRate(); assertEq(ers.interestLeft, yield - 23809523809523809523); // move close to end of smearing vm.warp(block.timestamp + 11 days); - fourSixTwoSixAgg.gulp(); - ers = fourSixTwoSixAgg.getAggregationVaultSavingRate(); + aggregationLayerVault.gulp(); + ers = aggregationLayerVault.getAggregationVaultSavingRate(); // mock a decrease of strategy balance by ers.interestLeft - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - (ers.interestLeft * 2); vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encodeWithSelector(EVault.balanceOf.selector, address(aggregationLayerVault)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); + aggregationLayerVault.harvest(address(eTST)); } } diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index 6b6efddb..4687df28 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -2,10 +2,14 @@ pragma solidity ^0.8.0; import { - FourSixTwoSixAggBase, FourSixTwoSixAgg, console2, EVault, Strategy -} from "../common/FourSixTwoSixAggBase.t.sol"; - -contract HarvestTest is FourSixTwoSixAggBase { + AggregationLayerVaultBase, + AggregationLayerVault, + console2, + EVault, + Strategy +} from "../common/AggregationLayerVaultBase.t.sol"; + +contract HarvestTest is AggregationLayerVaultBase { uint256 user1InitialBalance = 100000e18; uint256 amountToDeposit = 10000e18; @@ -19,76 +23,77 @@ contract HarvestTest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); } } function testHarvest() public { // no yield increase - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); - uint256 totalAllocatedBefore = fourSixTwoSixAgg.totalAllocated(); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + uint256 totalAllocatedBefore = aggregationLayerVault.totalAllocated(); - assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) == strategyBefore.allocated); + assertTrue(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))) == strategyBefore.allocated); vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); + aggregationLayerVault.harvest(address(eTST)); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated); - assertEq(fourSixTwoSixAgg.totalAllocated(), totalAllocatedBefore); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated); + assertEq(aggregationLayerVault.totalAllocated(), totalAllocatedBefore); // positive yield vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encodeWithSelector(EVault.balanceOf.selector, address(aggregationLayerVault)), abi.encode(aggrCurrentStrategyBalance * 11e17 / 1e18) ); - assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) > strategyBefore.allocated); - uint256 expectedAllocated = eTST.maxWithdraw(address(fourSixTwoSixAgg)); + assertTrue(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))) > strategyBefore.allocated); + uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); + aggregationLayerVault.harvest(address(eTST)); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, expectedAllocated); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedAllocated); assertEq( - fourSixTwoSixAgg.totalAllocated(), totalAllocatedBefore + (expectedAllocated - strategyBefore.allocated) + aggregationLayerVault.totalAllocated(), + totalAllocatedBefore + (expectedAllocated - strategyBefore.allocated) ); } @@ -96,49 +101,49 @@ contract HarvestTest is FourSixTwoSixAggBase { vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encodeWithSelector(EVault.balanceOf.selector, address(aggregationLayerVault)), abi.encode(aggrCurrentStrategyBalance * 9e17 / 1e18) ); - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated); + assertTrue(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))) < strategyBefore.allocated); vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); + aggregationLayerVault.harvest(address(eTST)); } function testHarvestNegativeYieldAndWithdrawSingleUser() public { vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encodeWithSelector(EVault.balanceOf.selector, address(aggregationLayerVault)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); - assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated); - uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(fourSixTwoSixAgg)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + assertTrue(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))) < strategyBefore.allocated); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(aggregationLayerVault)); - uint256 user1SharesBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 user1SocializedLoss = user1SharesBefore * negativeYield / fourSixTwoSixAgg.totalSupply(); + uint256 user1SharesBefore = aggregationLayerVault.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / aggregationLayerVault.totalSupply(); uint256 expectedUser1Assets = - user1SharesBefore * amountToDeposit / fourSixTwoSixAgg.totalSupply() - user1SocializedLoss; + user1SharesBefore * amountToDeposit / aggregationLayerVault.totalSupply() - user1SocializedLoss; uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); - fourSixTwoSixAgg.redeem(user1SharesBefore, user1, user1); + aggregationLayerVault.harvest(address(eTST)); + aggregationLayerVault.redeem(user1SharesBefore, user1, user1); vm.stopPrank(); - uint256 user1SharesAfter = fourSixTwoSixAgg.balanceOf(user1); + uint256 user1SharesAfter = aggregationLayerVault.balanceOf(user1); assertEq(user1SharesAfter, 0); assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedUser1Assets, 1); @@ -150,42 +155,42 @@ contract HarvestTest is FourSixTwoSixAggBase { // deposit into aggregator { vm.startPrank(user2); - assetTST.approve(address(fourSixTwoSixAgg), user2InitialBalance); - fourSixTwoSixAgg.deposit(user2InitialBalance, user2); + assetTST.approve(address(aggregationLayerVault), user2InitialBalance); + aggregationLayerVault.deposit(user2InitialBalance, user2); vm.stopPrank(); } vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(fourSixTwoSixAgg)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(fourSixTwoSixAgg)), + abi.encodeWithSelector(EVault.balanceOf.selector, address(aggregationLayerVault)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); - assertTrue(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))) < strategyBefore.allocated); - uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(fourSixTwoSixAgg)); - uint256 user1SharesBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 user1SocializedLoss = user1SharesBefore * negativeYield / fourSixTwoSixAgg.totalSupply(); - uint256 user2SharesBefore = fourSixTwoSixAgg.balanceOf(user2); - uint256 user2SocializedLoss = user2SharesBefore * negativeYield / fourSixTwoSixAgg.totalSupply(); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + assertTrue(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))) < strategyBefore.allocated); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(aggregationLayerVault)); + uint256 user1SharesBefore = aggregationLayerVault.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / aggregationLayerVault.totalSupply(); + uint256 user2SharesBefore = aggregationLayerVault.balanceOf(user2); + uint256 user2SocializedLoss = user2SharesBefore * negativeYield / aggregationLayerVault.totalSupply(); uint256 expectedUser1Assets = user1SharesBefore * (amountToDeposit + user2InitialBalance) - / fourSixTwoSixAgg.totalSupply() - user1SocializedLoss; + / aggregationLayerVault.totalSupply() - user1SocializedLoss; uint256 expectedUser2Assets = user2SharesBefore * (amountToDeposit + user2InitialBalance) - / fourSixTwoSixAgg.totalSupply() - user2SocializedLoss; + / aggregationLayerVault.totalSupply() - user2SocializedLoss; vm.prank(user1); - fourSixTwoSixAgg.harvest(address(eTST)); + aggregationLayerVault.harvest(address(eTST)); - uint256 user1SharesAfter = fourSixTwoSixAgg.balanceOf(user1); - uint256 user1AssetsAfter = fourSixTwoSixAgg.convertToAssets(user1SharesAfter); - uint256 user2SharesAfter = fourSixTwoSixAgg.balanceOf(user2); - uint256 user2AssetsAfter = fourSixTwoSixAgg.convertToAssets(user2SharesAfter); + uint256 user1SharesAfter = aggregationLayerVault.balanceOf(user1); + uint256 user1AssetsAfter = aggregationLayerVault.convertToAssets(user1SharesAfter); + uint256 user2SharesAfter = aggregationLayerVault.balanceOf(user2); + uint256 user2AssetsAfter = aggregationLayerVault.convertToAssets(user2SharesAfter); assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets, 1); assertApproxEqAbs(user2AssetsAfter, expectedUser2Assets, 1); diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index 23378e97..ca0f69fb 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -2,17 +2,17 @@ pragma solidity ^0.8.0; import { - FourSixTwoSixAggBase, - FourSixTwoSixAgg, + AggregationLayerVaultBase, + AggregationLayerVault, console2, EVault, IEVault, IRMTestDefault, TestERC20, Strategy -} from "../common/FourSixTwoSixAggBase.t.sol"; +} from "../common/AggregationLayerVaultBase.t.sol"; -contract RebalanceTest is FourSixTwoSixAggBase { +contract RebalanceTest is AggregationLayerVaultBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -29,40 +29,41 @@ contract RebalanceTest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() * strategyBefore.allocationPoints + / aggregationLayerVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); assertEq( - (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash + (aggregationLayerVault.getStrategy(address(eTST))).allocated, + strategyBefore.allocated + expectedStrategyCash ); } @@ -71,45 +72,47 @@ contract RebalanceTest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() * strategyBefore.allocationPoints + / aggregationLayerVault.totalAllocationPoints(); uint256 expectedToDeposit = expectedStrategyCash - strategyBefore.allocated; uint256 eTSTMaxDeposit = expectedToDeposit * 7e17 / 1e18; // mock max deposit vm.mockCall( - address(eTST), abi.encodeCall(eTST.maxDeposit, (address(fourSixTwoSixAgg))), abi.encode(eTSTMaxDeposit) + address(eTST), abi.encodeCall(eTST.maxDeposit, (address(aggregationLayerVault))), abi.encode(eTSTMaxDeposit) ); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), eTSTMaxDeposit); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), eTSTMaxDeposit); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated + eTSTMaxDeposit); + assertEq(aggregationLayerVault.totalAllocated(), eTSTMaxDeposit); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), eTSTMaxDeposit); + assertEq( + (aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated + eTSTMaxDeposit + ); } function testRebalanceByDepositingWhenToDepositIsGreaterThanCashAvailable() public { @@ -117,19 +120,19 @@ contract RebalanceTest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -138,7 +141,7 @@ contract RebalanceTest is FourSixTwoSixAggBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); // create new strategy & add it IEVault eTSTsecondary; @@ -155,29 +158,32 @@ contract RebalanceTest is FourSixTwoSixAggBase { // rebalance into eTSTsecondary vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTSTsecondary)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTSTsecondary)); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated ); - uint256 targetCash = fourSixTwoSixAgg.totalAssetsAllocatable() - * fourSixTwoSixAgg.getStrategy(address(0)).allocationPoints / fourSixTwoSixAgg.totalAllocationPoints(); - uint256 currentCash = fourSixTwoSixAgg.totalAssetsAllocatable() - fourSixTwoSixAgg.totalAllocated(); + uint256 targetCash = aggregationLayerVault.totalAssetsAllocatable() + * aggregationLayerVault.getStrategy(address(0)).allocationPoints + / aggregationLayerVault.totalAllocationPoints(); + uint256 currentCash = + aggregationLayerVault.totalAssetsAllocatable() - aggregationLayerVault.totalAllocated(); uint256 expectedStrategyCash = currentCash - targetCash; vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTSTsecondary); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - // assertEq(fourSixTwoSixAgg.totalAllocated(), eTSTsecondaryMaxDeposit); + // assertEq(aggregationLayerVault.totalAllocated(), eTSTsecondaryMaxDeposit); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), + expectedStrategyCash ); assertEq( - (fourSixTwoSixAgg.getStrategy(address(eTSTsecondary))).allocated, + (aggregationLayerVault.getStrategy(address(eTSTsecondary))).allocated, strategyBefore.allocated + expectedStrategyCash ); } @@ -188,42 +194,42 @@ contract RebalanceTest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); uint256 eTSTMaxDeposit = 0; // mock max deposit vm.mockCall( - address(eTST), abi.encodeCall(eTST.maxDeposit, (address(fourSixTwoSixAgg))), abi.encode(eTSTMaxDeposit) + address(eTST), abi.encodeCall(eTST.maxDeposit, (address(aggregationLayerVault))), abi.encode(eTSTMaxDeposit) ); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); - assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated); + assertEq(aggregationLayerVault.totalAllocated(), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated); } function testRebalanceByWithdrawing() public { @@ -231,19 +237,19 @@ contract RebalanceTest is FourSixTwoSixAggBase { // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -252,53 +258,52 @@ contract RebalanceTest is FourSixTwoSixAggBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; vm.prank(manager); - fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), newAllocationPoints); + aggregationLayerVault.adjustAllocationPoints(address(eTST), newAllocationPoints); vm.warp(block.timestamp + 86400); - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() * strategyBefore.allocationPoints + / aggregationLayerVault.totalAllocationPoints(); vm.prank(user1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - assertEq(fourSixTwoSixAgg.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), expectedStrategyCash); + assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); assertEq( - (fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, + (aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated - (strategyBefore.allocated - expectedStrategyCash) ); } - /// TODO: update this test function testRebalanceByWithdrawingWhenToWithdrawIsGreaterThanMaxWithdraw() public { uint256 amountToDeposit = 10000e18; // deposit into aggregator { - uint256 balanceBefore = fourSixTwoSixAgg.balanceOf(user1); - uint256 totalSupplyBefore = fourSixTwoSixAgg.totalSupply(); - uint256 totalAssetsDepositedBefore = fourSixTwoSixAgg.totalAssetsDeposited(); + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(fourSixTwoSixAgg), amountToDeposit); - fourSixTwoSixAgg.deposit(amountToDeposit, user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(fourSixTwoSixAgg.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(fourSixTwoSixAgg.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -307,36 +312,38 @@ contract RebalanceTest is FourSixTwoSixAggBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; vm.prank(manager); - fourSixTwoSixAgg.adjustAllocationPoints(address(eTST), newAllocationPoints); + aggregationLayerVault.adjustAllocationPoints(address(eTST), newAllocationPoints); vm.warp(block.timestamp + 86400); - Strategy memory strategyBefore = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = fourSixTwoSixAgg.totalAssetsAllocatable() * strategyBefore.allocationPoints - / fourSixTwoSixAgg.totalAllocationPoints(); + uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() * strategyBefore.allocationPoints + / aggregationLayerVault.totalAllocationPoints(); uint256 expectedToWithdraw = strategyBefore.allocated - expectedStrategyCash; uint256 eTSTMaxWithdraw = expectedToWithdraw * 7e17 / 1e18; // mock max withdraw vm.mockCall( - address(eTST), abi.encodeCall(eTST.maxWithdraw, (address(fourSixTwoSixAgg))), abi.encode(eTSTMaxWithdraw) + address(eTST), + abi.encodeCall(eTST.maxWithdraw, (address(aggregationLayerVault))), + abi.encode(eTSTMaxWithdraw) ); vm.prank(user1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(fourSixTwoSixAgg), strategiesToRebalance); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); - // assertEq(fourSixTwoSixAgg.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); + // assertEq(aggregationLayerVault.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); // assertEq( - // eTST.convertToAssets(eTST.balanceOf(address(fourSixTwoSixAgg))), strategyBefore.allocated - eTSTMaxWithdraw + // eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated - eTSTMaxWithdraw // ); - // assertEq((fourSixTwoSixAgg.getStrategy(address(eTST))).allocated, strategyBefore.allocated - eTSTMaxWithdraw); + // assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated - eTSTMaxWithdraw); } } diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index ea12437d..3846ccd2 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -1,9 +1,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg, IEVault, Strategy} from "../common/FourSixTwoSixAggBase.t.sol"; - -contract RemoveStrategyTest is FourSixTwoSixAggBase { +import { + AggregationLayerVaultBase, + AggregationLayerVault, + IEVault, + Strategy +} from "../common/AggregationLayerVaultBase.t.sol"; + +contract RemoveStrategyTest is AggregationLayerVaultBase { uint256 strategyAllocationPoints; IEVault anotherStrategy; @@ -16,17 +21,17 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { } function testRemoveStrategy() public { - uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = aggregationLayerVault.totalAllocationPoints(); uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); - fourSixTwoSixAgg.removeStrategy(address(eTST)); + aggregationLayerVault.removeStrategy(address(eTST)); - Strategy memory strategyAfter = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyAfter = aggregationLayerVault.getStrategy(address(eTST)); assertEq(strategyAfter.active, false); assertEq(strategyAfter.allocationPoints, 0); - assertEq(fourSixTwoSixAgg.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); + assertEq(aggregationLayerVault.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore - 1); } @@ -36,17 +41,17 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { ); _addStrategy(manager, address(anotherStrategy), strategyAllocationPoints); - uint256 totalAllocationPointsBefore = fourSixTwoSixAgg.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = aggregationLayerVault.totalAllocationPoints(); uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); - fourSixTwoSixAgg.removeStrategy(address(eTST)); + aggregationLayerVault.removeStrategy(address(eTST)); - Strategy memory strategyAfter = fourSixTwoSixAgg.getStrategy(address(eTST)); + Strategy memory strategyAfter = aggregationLayerVault.getStrategy(address(eTST)); assertEq(strategyAfter.active, false); assertEq(strategyAfter.allocationPoints, 0); - assertEq(fourSixTwoSixAgg.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); + assertEq(aggregationLayerVault.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore - 1); } @@ -70,7 +75,7 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { assertEq(withdrawalQueue[3], strategy3); vm.prank(manager); - fourSixTwoSixAgg.removeStrategy(strategy2); + aggregationLayerVault.removeStrategy(strategy2); withdrawalQueue = _getWithdrawalQueue(); assertEq(withdrawalQueue.length, 3); @@ -79,7 +84,7 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { assertEq(withdrawalQueue[2], strategy3); vm.prank(manager); - fourSixTwoSixAgg.removeStrategy(strategy3); + aggregationLayerVault.removeStrategy(strategy3); withdrawalQueue = _getWithdrawalQueue(); assertEq(withdrawalQueue.length, 2); @@ -87,14 +92,14 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { assertEq(withdrawalQueue[1], strategy1); vm.prank(manager); - fourSixTwoSixAgg.removeStrategy(address(eTST)); + aggregationLayerVault.removeStrategy(address(eTST)); withdrawalQueue = _getWithdrawalQueue(); assertEq(withdrawalQueue.length, 1); assertEq(withdrawalQueue[0], strategy1); vm.prank(manager); - fourSixTwoSixAgg.removeStrategy(strategy1); + aggregationLayerVault.removeStrategy(strategy1); withdrawalQueue = _getWithdrawalQueue(); assertEq(withdrawalQueue.length, 0); @@ -103,12 +108,12 @@ contract RemoveStrategyTest is FourSixTwoSixAggBase { function testRemoveStrategy_fromUnauthorized() public { vm.prank(deployer); vm.expectRevert(); - fourSixTwoSixAgg.removeStrategy(address(eTST)); + aggregationLayerVault.removeStrategy(address(eTST)); } function testRemoveStrategy_AlreadyRemoved() public { vm.prank(manager); vm.expectRevert(); - fourSixTwoSixAgg.removeStrategy(address(eTST2)); + aggregationLayerVault.removeStrategy(address(eTST2)); } } diff --git a/test/unit/ReorderWithdrawalQueueTest.t.sol b/test/unit/ReorderWithdrawalQueueTest.t.sol index 019ee516..3251194e 100644 --- a/test/unit/ReorderWithdrawalQueueTest.t.sol +++ b/test/unit/ReorderWithdrawalQueueTest.t.sol @@ -1,9 +1,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {FourSixTwoSixAggBase, FourSixTwoSixAgg, IEVault, WithdrawalQueue} from "../common/FourSixTwoSixAggBase.t.sol"; - -contract ReorderWithdrawalQueueTest is FourSixTwoSixAggBase { +import { + AggregationLayerVaultBase, + AggregationLayerVault, + IEVault, + WithdrawalQueue +} from "../common/AggregationLayerVaultBase.t.sol"; + +contract ReorderWithdrawalQueueTest is AggregationLayerVaultBase { uint256 eTSTAllocationPoints = 500e18; uint256 eTSTsecondaryAllocationPoints = 700e18; @@ -25,14 +30,18 @@ contract ReorderWithdrawalQueueTest is FourSixTwoSixAggBase { } function testReorderWithdrawalQueue() public { - assertEq(fourSixTwoSixAgg.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTAllocationPoints); - assertEq(fourSixTwoSixAgg.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTsecondaryAllocationPoints); + assertEq(aggregationLayerVault.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTAllocationPoints); + assertEq( + aggregationLayerVault.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTsecondaryAllocationPoints + ); vm.prank(manager); withdrawalQueue.reorderWithdrawalQueue(0, 1); - assertEq(fourSixTwoSixAgg.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTsecondaryAllocationPoints); - assertEq(fourSixTwoSixAgg.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTAllocationPoints); + assertEq( + aggregationLayerVault.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTsecondaryAllocationPoints + ); + assertEq(aggregationLayerVault.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTAllocationPoints); } function testReorderWithdrawalQueueWhenOutOfBounds() public { From a4752948c874b40999e064bd21b322e5d77dc9dd Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 19 Jun 2024 12:12:50 +0300 Subject: [PATCH 183/316] chore: renames --- src/AggregationLayerVaultFactory.sol | 8 ++++---- src/Dispatch.sol | 6 +++--- src/Shared.sol | 6 ++++-- src/{modules => module}/AllocationPoints.sol | 0 src/{modules => module}/Fee.sol | 0 src/{modules => module}/Hooks.sol | 0 src/{modules => module}/Rewards.sol | 0 src/{ => plugin}/Rebalancer.sol | 3 ++- src/{ => plugin}/WithdrawalQueue.sol | 2 +- test/common/AggregationLayerVaultBase.t.sol | 19 +++++++++++-------- test/e2e/BalanceForwarderE2ETest.t.sol | 2 +- 11 files changed, 26 insertions(+), 20 deletions(-) rename src/{modules => module}/AllocationPoints.sol (100%) rename src/{modules => module}/Fee.sol (100%) rename src/{modules => module}/Hooks.sol (100%) rename src/{modules => module}/Rewards.sol (100%) rename src/{ => plugin}/Rebalancer.sol (97%) rename src/{ => plugin}/WithdrawalQueue.sol (99%) diff --git a/src/AggregationLayerVaultFactory.sol b/src/AggregationLayerVaultFactory.sol index e105e3bd..fa22c4c0 100644 --- a/src/AggregationLayerVaultFactory.sol +++ b/src/AggregationLayerVaultFactory.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.0; import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; // core modules -import {Rewards} from "./modules/Rewards.sol"; -import {Hooks} from "./modules/Hooks.sol"; -import {Fee} from "./modules/Fee.sol"; +import {Rewards} from "./module/Rewards.sol"; +import {Hooks} from "./module/Hooks.sol"; +import {Fee} from "./module/Fee.sol"; // core plugins -import {WithdrawalQueue} from "./WithdrawalQueue.sol"; +import {WithdrawalQueue} from "./plugin/WithdrawalQueue.sol"; import {AggregationLayerVault} from "./AggregationLayerVault.sol"; contract AggregationLayerVaultFactory { diff --git a/src/Dispatch.sol b/src/Dispatch.sol index f485d408..465c43d9 100644 --- a/src/Dispatch.sol +++ b/src/Dispatch.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-2.0-or-later - pragma solidity ^0.8.0; +//contracts import {Shared} from "./Shared.sol"; -import {HooksModule} from "./modules/Hooks.sol"; -import {RewardsModule} from "./modules/Rewards.sol"; +import {HooksModule} from "./module/Hooks.sol"; +import {RewardsModule} from "./module/Rewards.sol"; abstract contract Dispatch is RewardsModule, HooksModule { address public immutable MODULE_REWARDS; diff --git a/src/Shared.sol b/src/Shared.sol index 8d8d9f2b..c87a970f 100644 --- a/src/Shared.sol +++ b/src/Shared.sol @@ -1,10 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; -import {ErrorsLib} from "./lib/ErrorsLib.sol"; +// interfaces import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; +// libs +import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; +import {ErrorsLib} from "./lib/ErrorsLib.sol"; import {HooksLib} from "./lib/HooksLib.sol"; contract Shared { diff --git a/src/modules/AllocationPoints.sol b/src/module/AllocationPoints.sol similarity index 100% rename from src/modules/AllocationPoints.sol rename to src/module/AllocationPoints.sol diff --git a/src/modules/Fee.sol b/src/module/Fee.sol similarity index 100% rename from src/modules/Fee.sol rename to src/module/Fee.sol diff --git a/src/modules/Hooks.sol b/src/module/Hooks.sol similarity index 100% rename from src/modules/Hooks.sol rename to src/module/Hooks.sol diff --git a/src/modules/Rewards.sol b/src/module/Rewards.sol similarity index 100% rename from src/modules/Rewards.sol rename to src/module/Rewards.sol diff --git a/src/Rebalancer.sol b/src/plugin/Rebalancer.sol similarity index 97% rename from src/Rebalancer.sol rename to src/plugin/Rebalancer.sol index e783d700..86f4c42d 100644 --- a/src/Rebalancer.sol +++ b/src/plugin/Rebalancer.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {IFourSixTwoSixAgg, Strategy} from "./interface/IFourSixTwoSixAgg.sol"; +// interfaces +import {IFourSixTwoSixAgg, Strategy} from "../interface/IFourSixTwoSixAgg.sol"; import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; contract Rebalancer { diff --git a/src/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol similarity index 99% rename from src/WithdrawalQueue.sol rename to src/plugin/WithdrawalQueue.sol index 9a409ef8..2349355b 100644 --- a/src/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; // interfaces import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; // internal dep -import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; +import {IFourSixTwoSixAgg} from "../interface/IFourSixTwoSixAgg.sol"; // contracts import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; diff --git a/test/common/AggregationLayerVaultBase.t.sol b/test/common/AggregationLayerVaultBase.t.sol index 988011ec..a945e6f9 100644 --- a/test/common/AggregationLayerVaultBase.t.sol +++ b/test/common/AggregationLayerVaultBase.t.sol @@ -1,18 +1,21 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +// interfaces +import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; +import {IWithdrawalQueue} from "../../src/interface/IWithdrawalQueue.sol"; +// contracts import "evk/test/unit/evault/EVaultTestBase.t.sol"; import {AggregationLayerVault, Strategy} from "../../src/AggregationLayerVault.sol"; -import {Rebalancer} from "../../src/Rebalancer.sol"; -import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; -import {Hooks, HooksModule} from "../../src/modules/Hooks.sol"; -import {Rewards} from "../../src/modules/Rewards.sol"; -import {Fee} from "../../src/modules/Fee.sol"; +import {Rebalancer} from "../../src/plugin/Rebalancer.sol"; +import {Hooks, HooksModule} from "../../src/module/Hooks.sol"; +import {Rewards} from "../../src/module/Rewards.sol"; +import {Fee} from "../../src/module/Fee.sol"; import {AggregationLayerVaultFactory} from "../../src/AggregationLayerVaultFactory.sol"; -import {WithdrawalQueue} from "../../src/WithdrawalQueue.sol"; -import {IWithdrawalQueue} from "../../src/interface/IWithdrawalQueue.sol"; +import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; +import {AllocationPoints} from "../../src/module/AllocationPoints.sol"; +// libs import {ErrorsLib} from "../../src/lib/ErrorsLib.sol"; -import {AllocationPoints} from "../../src/modules/AllocationPoints.sol"; contract AggregationLayerVaultBase is EVaultTestBase { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 3b6ea6fc..45fea881 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol"; import { AggregationLayerVaultBase, AggregationLayerVault, @@ -12,7 +13,6 @@ import { AggregationLayerVaultFactory, Rewards } from "../common/AggregationLayerVaultBase.t.sol"; -import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol"; contract BalanceForwarderE2ETest is AggregationLayerVaultBase { uint256 user1InitialBalance = 100000e18; From 75fcb7f22901a6d70de3c400afd7398330e1d075 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:13:54 +0300 Subject: [PATCH 184/316] fix RewardStreams integration --- foundry.toml | 2 +- src/AggregationLayerVault.sol | 3 +- src/AggregationLayerVaultFactory.sol | 62 +++++++++++---------- src/interface/IWithdrawalQueue.sol | 2 +- src/lib/EventsLib.sol | 10 ++-- src/lib/StorageLib.sol | 1 - src/module/Rewards.sol | 37 +++++++++--- src/plugin/WithdrawalQueue.sol | 4 +- test/common/AggregationLayerVaultBase.t.sol | 22 ++++---- test/e2e/BalanceForwarderE2ETest.t.sol | 21 +++---- 10 files changed, 95 insertions(+), 69 deletions(-) diff --git a/foundry.toml b/foundry.toml index c1df1e59..32d5e99e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,7 +4,7 @@ out = "out" libs = ["lib"] test = 'test' optimizer = true -optimizer_runs = 1000 +optimizer_runs = 500 # solc = "0.8.0" gas_reports = ["*"] fs_permissions = [{ access = "read", path = "./"}] diff --git a/src/AggregationLayerVault.sol b/src/AggregationLayerVault.sol index 074ac10e..dca7a98a 100644 --- a/src/AggregationLayerVault.sol +++ b/src/AggregationLayerVault.sol @@ -98,8 +98,7 @@ contract AggregationLayerVault is }); $.totalAllocationPoints = _initParams.initialCashAllocationPoints; $.evc = _initParams.evc; - - _setBalanceTracker(_initParams.balanceTracker); + $.balanceTracker = _initParams.balanceTracker; // Setup DEFAULT_ADMIN _grantRole(DEFAULT_ADMIN_ROLE, _initParams.aggregationVaultOwner); diff --git a/src/AggregationLayerVaultFactory.sol b/src/AggregationLayerVaultFactory.sol index fa22c4c0..5871eb0d 100644 --- a/src/AggregationLayerVaultFactory.sol +++ b/src/AggregationLayerVaultFactory.sol @@ -1,49 +1,51 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; -// core modules +// contracts import {Rewards} from "./module/Rewards.sol"; import {Hooks} from "./module/Hooks.sol"; import {Fee} from "./module/Fee.sol"; -// core plugins import {WithdrawalQueue} from "./plugin/WithdrawalQueue.sol"; import {AggregationLayerVault} from "./AggregationLayerVault.sol"; +// libs +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; contract AggregationLayerVaultFactory { /// core dependencies address public immutable evc; address public immutable balanceTracker; - /// core modules + /// core modules implementations addresses address public immutable rewardsModuleImpl; address public immutable hooksModuleImpl; address public immutable feeModuleImpl; - address public immutable allocationpointsModuleImpl; + address public immutable allocationPointsModuleImpl; /// plugins - /// @dev Rebalancer periphery, one instance can serve different aggregation vaults - address public immutable rebalancerAddr; - /// @dev Withdrawal queue perihperhy, need to be deployed per aggregation vault - address public immutable withdrawalQueueAddr; + /// @dev Rebalancer plugin contract address, one instance can serve different aggregation vaults + address public immutable rebalancer; + /// @dev WithdrawalQueue plugin implementation address, need to be deployed per aggregation vault + address public immutable withdrawalQueueImpl; + + struct FactoryParams { + address evc; + address balanceTracker; + address rewardsModuleImpl; + address hooksModuleImpl; + address feeModuleImpl; + address allocationPointsModuleImpl; + address rebalancer; + address withdrawalQueueImpl; + } - constructor( - address _evc, - address _balanceTracker, - address _rewardsModuleImpl, - address _hooksModuleImpl, - address _feeModuleImpl, - address _allocationpointsModuleImpl, - address _rebalancerAddr, - address _withdrawalQueueAddr - ) { - evc = _evc; - balanceTracker = _balanceTracker; - rewardsModuleImpl = _rewardsModuleImpl; - hooksModuleImpl = _hooksModuleImpl; - feeModuleImpl = _feeModuleImpl; - allocationpointsModuleImpl = _allocationpointsModuleImpl; + constructor(FactoryParams memory _factoryParams) { + evc = _factoryParams.evc; + balanceTracker = _factoryParams.balanceTracker; + rewardsModuleImpl = _factoryParams.rewardsModuleImpl; + hooksModuleImpl = _factoryParams.hooksModuleImpl; + feeModuleImpl = _factoryParams.feeModuleImpl; + allocationPointsModuleImpl = _factoryParams.allocationPointsModuleImpl; - rebalancerAddr = _rebalancerAddr; - withdrawalQueueAddr = _withdrawalQueueAddr; + rebalancer = _factoryParams.rebalancer; + withdrawalQueueImpl = _factoryParams.withdrawalQueueImpl; } function deployEulerAggregationLayer( @@ -56,10 +58,10 @@ contract AggregationLayerVaultFactory { address rewardsModuleAddr = Clones.clone(rewardsModuleImpl); address hooksModuleAddr = Clones.clone(hooksModuleImpl); address feeModuleAddr = Clones.clone(feeModuleImpl); - address allocationpointsModuleAddr = Clones.clone(allocationpointsModuleImpl); + address allocationpointsModuleAddr = Clones.clone(allocationPointsModuleImpl); // cloning plugins - WithdrawalQueue withdrawalQueue = WithdrawalQueue(Clones.clone(withdrawalQueueAddr)); + WithdrawalQueue withdrawalQueue = WithdrawalQueue(Clones.clone(withdrawalQueueImpl)); // deploy new aggregation vault AggregationLayerVault aggregationLayerVault = @@ -69,7 +71,7 @@ contract AggregationLayerVaultFactory { evc: evc, balanceTracker: balanceTracker, withdrawalQueuePeriphery: address(withdrawalQueue), - rebalancerPerihpery: rebalancerAddr, + rebalancerPerihpery: rebalancer, aggregationVaultOwner: msg.sender, asset: _asset, name: _name, diff --git a/src/interface/IWithdrawalQueue.sol b/src/interface/IWithdrawalQueue.sol index e5e68d0c..4026fd8c 100644 --- a/src/interface/IWithdrawalQueue.sol +++ b/src/interface/IWithdrawalQueue.sol @@ -16,5 +16,5 @@ interface IWithdrawalQueue { function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external; function withdrawalQueueLength() external view returns (uint256); - function withdrawalQueue(uint256 _index) external view returns (address); + // function withdrawalQueue(uint256 _index) external view returns (address); } diff --git a/src/lib/EventsLib.sol b/src/lib/EventsLib.sol index 952b8595..f2bd3bfe 100644 --- a/src/lib/EventsLib.sol +++ b/src/lib/EventsLib.sol @@ -6,12 +6,12 @@ library EventsLib { event Gulp(uint256 interestLeft, uint256 interestSmearEnd); event Harvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); event AccruePerformanceFee(address indexed feeRecipient, uint256 yield, uint256 feeAssets); - event Rebalance(address indexed strategy, uint256 _amountToRebalance, bool _isDeposit); + event Rebalance(address indexed strategy, uint256 amountToRebalance, bool isDeposit); /// @dev Allocationpoints events event AdjustAllocationPoints(address indexed strategy, uint256 oldPoints, uint256 newPoints); event AddStrategy(address indexed strategy, uint256 allocationPoints); - event RemoveStrategy(address indexed _strategy); + event RemoveStrategy(address indexed strategy); event SetStrategyCap(address indexed strategy, uint256 cap); /// @dev Fee events @@ -24,6 +24,8 @@ library EventsLib { /// @dev Rewards events event OptInStrategyRewards(address indexed strategy); event OptOutStrategyRewards(address indexed strategy); - event EnableBalanceForwarder(address indexed _user); - event DisableBalanceForwarder(address indexed _user); + event EnableBalanceForwarder(address indexed user); + event DisableBalanceForwarder(address indexed user); + event EnableRewardForStrategy(address indexed strategy, address indexed reward); + event DisableRewardForStrategy(address indexed strategy, address indexed reward, bool forfeitRecentReward); } diff --git a/src/lib/StorageLib.sol b/src/lib/StorageLib.sol index cab1b385..c5811a8c 100644 --- a/src/lib/StorageLib.sol +++ b/src/lib/StorageLib.sol @@ -44,7 +44,6 @@ struct AggregationVaultStorage { /// allocationPoints: number of points allocated to this strategy /// active: a boolean to indice if this strategy is active or not /// cap: an optional cap in terms of deposited underlying asset. By default, it is set to 0(not activated) - struct Strategy { uint120 allocated; uint120 allocationPoints; diff --git a/src/module/Rewards.sol b/src/module/Rewards.sol index 24fc79fd..4547b5d8 100644 --- a/src/module/Rewards.sol +++ b/src/module/Rewards.sol @@ -13,10 +13,11 @@ import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {ErrorsLib} from "../lib/ErrorsLib.sol"; import {EventsLib} from "../lib/EventsLib.sol"; -/// @title BalanceForwarder contract +/// @title Rewards module /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) -/// @notice A generic contract to integrate with https://github.com/euler-xyz/reward-streams +/// @notice A module to provide balancer tracking for reward streats and to integrate with strategies rewards. +/// @dev See https://github.com/euler-xyz/reward-streams. abstract contract RewardsModule is IBalanceForwarder, Shared { /// @notice Opt in to strategy rewards /// @param _strategy Strategy address @@ -38,6 +39,32 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { emit EventsLib.OptOutStrategyRewards(_strategy); } + function enableRewardForStrategy(address _strategy, address _reward) external virtual nonReentrant { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if (!$.strategies[_strategy].active) revert ErrorsLib.InactiveStrategy(); + + IRewardStreams(IBalanceForwarder(_strategy).balanceTrackerAddress()).enableReward(_strategy, _reward); + + emit EventsLib.EnableRewardForStrategy(_strategy, _reward); + } + + function disableRewardForStrategy(address _strategy, address _reward, bool _forfeitRecentReward) + external + virtual + nonReentrant + { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if (!$.strategies[_strategy].active) revert ErrorsLib.InactiveStrategy(); + + IRewardStreams(IBalanceForwarder(_strategy).balanceTrackerAddress()).disableReward( + _strategy, _reward, _forfeitRecentReward + ); + + emit EventsLib.DisableRewardForStrategy(_strategy, _reward, _forfeitRecentReward); + } + /// @notice Claim a specific strategy rewards /// @param _strategy Strategy address. /// @param _reward The address of the reward token. @@ -114,12 +141,6 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { emit EventsLib.DisableBalanceForwarder(_sender); } - function _setBalanceTracker(address _balancerTracker) internal { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - $.balanceTracker = _balancerTracker; - } - /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract /// @param _account Address to query /// @return True if balance forwarder is enabled diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index 2349355b..b5e43689 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.0; // interfaces import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; -// internal dep import {IFourSixTwoSixAgg} from "../interface/IFourSixTwoSixAgg.sol"; +import {IWithdrawalQueue} from "../interface/IWithdrawalQueue.sol"; // contracts import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; -contract WithdrawalQueue is AccessControlEnumerableUpgradeable { +contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue { error OutOfBounds(); error SameIndexes(); error NotEnoughAssets(); diff --git a/test/common/AggregationLayerVaultBase.t.sol b/test/common/AggregationLayerVaultBase.t.sol index a945e6f9..5754364d 100644 --- a/test/common/AggregationLayerVaultBase.t.sol +++ b/test/common/AggregationLayerVaultBase.t.sol @@ -53,16 +53,18 @@ contract AggregationLayerVaultBase is EVaultTestBase { rebalancer = new Rebalancer(); withdrawalQueueImpl = new WithdrawalQueue(); - aggregationLayerVaultFactory = new AggregationLayerVaultFactory( - address(evc), - address(0), - address(rewardsImpl), - address(hooksImpl), - address(feeModuleImpl), - address(allocationPointsModuleImpl), - address(rebalancer), - address(withdrawalQueueImpl) - ); + + AggregationLayerVaultFactory.FactoryParams memory factoryParams = AggregationLayerVaultFactory.FactoryParams({ + evc: address(evc), + balanceTracker: address(0), + rewardsModuleImpl: address(rewardsImpl), + hooksModuleImpl: address(hooksImpl), + feeModuleImpl: address(feeModuleImpl), + allocationPointsModuleImpl: address(allocationPointsModuleImpl), + rebalancer: address(rebalancer), + withdrawalQueueImpl: address(withdrawalQueueImpl) + }); + aggregationLayerVaultFactory = new AggregationLayerVaultFactory(factoryParams); aggregationLayerVault = AggregationLayerVault( aggregationLayerVaultFactory.deployEulerAggregationLayer( diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 45fea881..7ae48d56 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -25,16 +25,17 @@ contract BalanceForwarderE2ETest is AggregationLayerVaultBase { vm.startPrank(deployer); trackingReward = address(new TrackingRewardStreams(address(evc), 2 weeks)); - aggregationLayerVaultFactory = new AggregationLayerVaultFactory( - address(evc), - trackingReward, - address(rewardsImpl), - address(hooksImpl), - address(feeModuleImpl), - address(allocationPointsModuleImpl), - address(rebalancer), - address(withdrawalQueueImpl) - ); + AggregationLayerVaultFactory.FactoryParams memory factoryParams = AggregationLayerVaultFactory.FactoryParams({ + evc: address(evc), + balanceTracker: trackingReward, + rewardsModuleImpl: address(rewardsImpl), + hooksModuleImpl: address(hooksImpl), + feeModuleImpl: address(feeModuleImpl), + allocationPointsModuleImpl: address(allocationPointsModuleImpl), + rebalancer: address(rebalancer), + withdrawalQueueImpl: address(withdrawalQueueImpl) + }); + aggregationLayerVaultFactory = new AggregationLayerVaultFactory(factoryParams); aggregationLayerVault = AggregationLayerVault( aggregationLayerVaultFactory.deployEulerAggregationLayer( address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS From de41b893eff6a5e35d930af146b01ca830ad0a58 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 19 Jun 2024 13:20:12 +0300 Subject: [PATCH 185/316] clean --- src/interface/IWithdrawalQueue.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/interface/IWithdrawalQueue.sol b/src/interface/IWithdrawalQueue.sol index 4026fd8c..92cc6309 100644 --- a/src/interface/IWithdrawalQueue.sol +++ b/src/interface/IWithdrawalQueue.sol @@ -16,5 +16,4 @@ interface IWithdrawalQueue { function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external; function withdrawalQueueLength() external view returns (uint256); - // function withdrawalQueue(uint256 _index) external view returns (address); } From d8647561158e571bfc2188de90ed12587f32a8da Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 19 Jun 2024 17:24:44 +0300 Subject: [PATCH 186/316] add natspec and use() --- src/AggregationLayerVault.sol | 14 ++++++++++++++ src/module/Rewards.sol | 7 +++++++ 2 files changed, 21 insertions(+) diff --git a/src/AggregationLayerVault.sol b/src/AggregationLayerVault.sol index dca7a98a..0171d380 100644 --- a/src/AggregationLayerVault.sol +++ b/src/AggregationLayerVault.sol @@ -139,6 +139,20 @@ contract AggregationLayerVault is use(MODULE_REWARDS) {} + function enableRewardForStrategy(address _strategy, address _reward) + external + override + onlyRole(AGGREGATION_VAULT_MANAGER) + use(MODULE_REWARDS) + {} + + function disableRewardForStrategy(address _strategy, address _reward, bool _forfeitRecentReward) + external + override + onlyRole(AGGREGATION_VAULT_MANAGER) + use(MODULE_REWARDS) + {} + /// @notice Claim a specific strategy rewards /// @param _strategy Strategy address. /// @param _reward The address of the reward token. diff --git a/src/module/Rewards.sol b/src/module/Rewards.sol index 4547b5d8..69c785ac 100644 --- a/src/module/Rewards.sol +++ b/src/module/Rewards.sol @@ -39,6 +39,9 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { emit EventsLib.OptOutStrategyRewards(_strategy); } + /// @notice Enable aggregation layer vault rewards for specific strategy's reward token. + /// @param _strategy Strategy address. + /// @param _reward Reward token address. function enableRewardForStrategy(address _strategy, address _reward) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); @@ -49,6 +52,10 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { emit EventsLib.EnableRewardForStrategy(_strategy, _reward); } + /// @notice Disable aggregation layer vault rewards for specific strategy's reward token. + /// @param _strategy Strategy address. + /// @param _reward Reward token address. + /// @param _forfeitRecentReward Whether to forfeit the recent rewards or not. function disableRewardForStrategy(address _strategy, address _reward, bool _forfeitRecentReward) external virtual From b09b8d8518d733f8fdc4f08a89fef10834439f0b Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 19 Jun 2024 17:36:40 +0300 Subject: [PATCH 187/316] clean --- src/AggregationLayerVault.sol | 54 ++++++++++------------------------- 1 file changed, 15 insertions(+), 39 deletions(-) diff --git a/src/AggregationLayerVault.sol b/src/AggregationLayerVault.sol index 0171d380..1f1d24ff 100644 --- a/src/AggregationLayerVault.sol +++ b/src/AggregationLayerVault.sol @@ -113,16 +113,13 @@ contract AggregationLayerVault is _setRoleAdmin(REBALANCER, REBALANCER_ADMIN); } - /// @notice Set performance fee recipient address - /// @notice @param _newFeeRecipient Recipient address + /// @dev See {FeeModule-setFeeRecipient}. function setFeeRecipient(address _newFeeRecipient) external onlyRole(AGGREGATION_VAULT_MANAGER) use(MODULE_FEE) {} - /// @notice Set performance fee (1e18 == 100%) - /// @notice @param _newFee Fee rate + /// @dev See {FeeModule-setPerformanceFee}. function setPerformanceFee(uint256 _newFee) external onlyRole(AGGREGATION_VAULT_MANAGER) use(MODULE_FEE) {} - /// @notice Opt in to strategy rewards - /// @param _strategy Strategy address + /// @dev See {RewardsModule-optInStrategyRewards}. function optInStrategyRewards(address _strategy) external override @@ -130,8 +127,7 @@ contract AggregationLayerVault is use(MODULE_REWARDS) {} - /// @notice Opt out of strategy rewards - /// @param _strategy Strategy address + /// @dev See {RewardsModule-optOutStrategyRewards}. function optOutStrategyRewards(address _strategy) external override @@ -139,6 +135,7 @@ contract AggregationLayerVault is use(MODULE_REWARDS) {} + /// @dev See {RewardsModule-optOutStrategyRewards}. function enableRewardForStrategy(address _strategy, address _reward) external override @@ -146,6 +143,7 @@ contract AggregationLayerVault is use(MODULE_REWARDS) {} + /// @dev See {RewardsModule-disableRewardForStrategy}. function disableRewardForStrategy(address _strategy, address _reward, bool _forfeitRecentReward) external override @@ -153,11 +151,7 @@ contract AggregationLayerVault is use(MODULE_REWARDS) {} - /// @notice Claim a specific strategy rewards - /// @param _strategy Strategy address. - /// @param _reward The address of the reward token. - /// @param _recipient The address to receive the claimed reward tokens. - /// @param _forfeitRecentReward Whether to forfeit the recent rewards and not update the accumulator. + /// @dev See {RewardsModule-claimStrategyReward}. function claimStrategyReward(address _strategy, address _reward, address _recipient, bool _forfeitRecentReward) external override @@ -165,54 +159,37 @@ contract AggregationLayerVault is use(MODULE_REWARDS) {} - /// @notice Enables balance forwarding for sender - /// @dev Should call the IBalanceTracker hook with the current user's balance + /// @dev See {RewardsModule-enableBalanceForwarder}. function enableBalanceForwarder() external override use(MODULE_REWARDS) {} - /// @notice Disables balance forwarding for the sender - /// @dev Should call the IBalanceTracker hook with the account's balance of 0 + /// @dev See {RewardsModule-disableBalanceForwarder}. function disableBalanceForwarder() external override use(MODULE_REWARDS) {} - /// @notice Adjust a certain strategy's allocation points. - /// @dev Can only be called by an address that have the ALLOCATIONS_MANAGER - /// @param _strategy address of strategy - /// @param _newPoints new strategy's points + /// @dev See {AllocationPointsModule-adjustAllocationPoints}. function adjustAllocationPoints(address _strategy, uint256 _newPoints) external use(MODULE_ALLOCATION_POINTS) onlyRole(ALLOCATIONS_MANAGER) {} - /// @notice Set cap on strategy allocated amount. - /// @dev By default, cap is set to 0, not activated. - /// @param _strategy Strategy address. - /// @param _cap Cap amount + /// @dev See {AllocationPointsModule-setStrategyCap}. function setStrategyCap(address _strategy, uint256 _cap) external use(MODULE_ALLOCATION_POINTS) onlyRole(ALLOCATIONS_MANAGER) {} - /// @notice Add new strategy with it's allocation points. - /// @dev Can only be called by an address that have STRATEGY_ADDER. - /// @param _strategy Address of the strategy - /// @param _allocationPoints Strategy's allocation points + /// @dev See {AllocationPointsModule-addStrategy}. function addStrategy(address _strategy, uint256 _allocationPoints) external use(MODULE_ALLOCATION_POINTS) onlyRole(STRATEGY_ADDER) {} - /// @notice Remove strategy and set its allocation points to zero. - /// @dev This function does not pull funds, `harvest()` needs to be called to withdraw - /// @dev Can only be called by an address that have the STRATEGY_REMOVER - /// @param _strategy Address of the strategy + /// @dev See {AllocationPointsModule-removeStrategy}. function removeStrategy(address _strategy) external use(MODULE_ALLOCATION_POINTS) onlyRole(STRATEGY_REMOVER) {} - /// @notice Set hooks contract and hooked functions. - /// @dev This funtion should be overriden to implement access control. - /// @param _hooksTarget Hooks contract. - /// @param _hookedFns Hooked functions. + /// @dev See {HooksModule-setHooksConfig}. function setHooksConfig(address _hooksTarget, uint32 _hookedFns) external override @@ -436,8 +413,7 @@ contract AggregationLayerVault is /// @dev Withdraw asset back to the user. /// @dev See {IERC4626-_withdraw}. - /// @dev if the cash reserve can not cover the amount to withdraw, this function will loop through the strategies - /// to cover the remaining amount. This function will revert if the amount to withdraw is not available + /// @dev This function call WithdrawalQueue.callWithdrawalQueue() that should handle the rest of the withdraw execution flow. function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) internal override From da33430ba0fd1ed235b66b71fd1cae951de3ec55 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:05:47 +0300 Subject: [PATCH 188/316] update withdraw and redeem --- src/AggregationLayerVault.sol | 108 +++++++++++++++++++---------- src/Shared.sol | 8 ++- src/interface/IWithdrawalQueue.sol | 5 +- src/plugin/WithdrawalQueue.sol | 18 +++-- test/e2e/HooksE2ETest.t.sol | 2 +- 5 files changed, 95 insertions(+), 46 deletions(-) diff --git a/src/AggregationLayerVault.sol b/src/AggregationLayerVault.sol index 1f1d24ff..4544dfb0 100644 --- a/src/AggregationLayerVault.sol +++ b/src/AggregationLayerVault.sol @@ -281,11 +281,11 @@ contract AggregationLayerVault is ) external { _isCallerWithdrawalQueue(); + super._withdraw(caller, receiver, owner, assets, shares); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.totalAssetsDeposited -= assets; - super._withdraw(caller, receiver, owner, assets, shares); - _gulp(); } @@ -347,13 +347,43 @@ contract AggregationLayerVault is } /// @dev See {IERC4626-deposit}. + /// @dev Increate the total assets deposited. function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256) { - return super.deposit(assets, receiver); + uint256 maxAssets = maxDeposit(receiver); + if (assets > maxAssets) { + revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); + } + + address caller = _msgSender(); + _callHooksTarget(DEPOSIT, caller); + + uint256 shares = previewDeposit(assets); + super._deposit(caller, receiver, assets, shares); + + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + $.totalAssetsDeposited += assets; + + return shares; } /// @dev See {IERC4626-mint}. + /// @dev Increate the total assets deposited. function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256) { - return super.mint(shares, receiver); + uint256 maxShares = maxMint(receiver); + if (shares > maxShares) { + revert ERC4626ExceededMaxMint(receiver, shares, maxShares); + } + + address caller = _msgSender(); + _callHooksTarget(DEPOSIT, caller); + + uint256 assets = previewMint(shares); + super._deposit(caller, receiver, assets, shares); + + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + $.totalAssetsDeposited += assets; + + return assets; } /// @dev See {IERC4626-withdraw}. @@ -366,7 +396,25 @@ contract AggregationLayerVault is { // Move interest to totalAssetsDeposited _updateInterestAccrued(); - return super.withdraw(assets, receiver, owner); + + uint256 maxAssets = _convertToAssets(balanceOf(owner), Math.Rounding.Floor); + if (assets > maxAssets) { + revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); + } + + address caller = _msgSender(); + _callHooksTarget(WITHDRAW, caller); + + shares = _convertToShares(assets, Math.Rounding.Ceil); + + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); + + (, shares) = IWithdrawalQueue($.withdrawalQueue).callWithdrawalQueue( + caller, receiver, owner, assets, shares, assetsRetrieved, false + ); + + return shares; } /// @dev See {IERC4626-redeem}. @@ -379,7 +427,25 @@ contract AggregationLayerVault is { // Move interest to totalAssetsDeposited _updateInterestAccrued(); - return super.redeem(shares, receiver, owner); + + uint256 maxShares = balanceOf(owner); + if (shares > maxShares) { + revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); + } + + address caller = _msgSender(); + _callHooksTarget(REDEEM, caller); + + assets = _convertToAssets(shares, Math.Rounding.Floor); + + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); + + (assets,) = IWithdrawalQueue($.withdrawalQueue).callWithdrawalQueue( + caller, receiver, owner, assets, shares, assetsRetrieved, true + ); + + return assets; } /// @notice Return the total amount of assets deposited, plus the accrued interest. @@ -399,36 +465,6 @@ contract AggregationLayerVault is return IERC20(asset()).balanceOf(address(this)) + $.totalAllocated; } - /// @dev Increate the total assets deposited, and call IERC4626._deposit() - /// @dev See {IERC4626-_deposit}. - function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal override { - _callHooksTarget(DEPOSIT, caller); - - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - $.totalAssetsDeposited += assets; - - super._deposit(caller, receiver, assets, shares); - } - - /// @dev Withdraw asset back to the user. - /// @dev See {IERC4626-_withdraw}. - /// @dev This function call WithdrawalQueue.callWithdrawalQueue() that should handle the rest of the withdraw execution flow. - function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) - internal - override - { - _callHooksTarget(WITHDRAW, caller); - - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); - - IWithdrawalQueue($.withdrawalQueue).callWithdrawalQueue( - caller, receiver, owner, assets, shares, assetsRetrieved - ); - } - /// @notice update accrued interest. function _updateInterestAccrued() internal { uint256 accruedInterest = _interestAccruedFromCache(); diff --git a/src/Shared.sol b/src/Shared.sol index c87a970f..8054e196 100644 --- a/src/Shared.sol +++ b/src/Shared.sol @@ -17,10 +17,12 @@ contract Shared { uint32 public constant DEPOSIT = 1 << 0; uint32 public constant WITHDRAW = 1 << 1; - uint32 public constant ADD_STRATEGY = 1 << 2; - uint32 public constant REMOVE_STRATEGY = 1 << 3; + uint32 public constant MINT = 1 << 2; + uint32 public constant REDEEM = 1 << 3; + uint32 public constant ADD_STRATEGY = 1 << 4; + uint32 public constant REMOVE_STRATEGY = 1 << 5; - uint32 constant ACTIONS_COUNTER = 1 << 4; + uint32 constant ACTIONS_COUNTER = 1 << 6; uint256 constant HOOKS_MASK = 0x00000000000000000000000000000000000000000000000000000000FFFFFFFF; modifier nonReentrant() { diff --git a/src/interface/IWithdrawalQueue.sol b/src/interface/IWithdrawalQueue.sol index 92cc6309..47143532 100644 --- a/src/interface/IWithdrawalQueue.sol +++ b/src/interface/IWithdrawalQueue.sol @@ -11,8 +11,9 @@ interface IWithdrawalQueue { address owner, uint256 assets, uint256 shares, - uint256 availableAssets - ) external; + uint256 availableAssets, + bool _isRedeem + ) external returns (uint256, uint256); function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external; function withdrawalQueueLength() external view returns (uint256); diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index b5e43689..dd798788 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -104,14 +104,17 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue /// @param assets Amount of asset to withdraw. /// @param shares Amount of shares to burn. /// @param availableAssets Amount of available asset in aggregation layer vault's cash reserve. + /// @return uint256 The amount of assets withdrawn from the aggregation layer vault. + /// @return uint256 The amount of aggregation layer vault shares burned. function callWithdrawalQueue( address caller, address receiver, address owner, uint256 assets, uint256 shares, - uint256 availableAssets - ) external { + uint256 availableAssets, + bool _isRedeem + ) external returns (uint256, uint256) { _isCallerAggregationVault(); WithdrawalQueueStorage memory $ = _getWithdrawalQueueStorage(); @@ -140,8 +143,13 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue } } - // re-calculate shares in case of socialized loss - shares = IERC4626(eulerAggregationVaultCached).previewWithdraw(assets); + if (_isRedeem) { + // re-calculate assets in case of socialized loss + assets = IERC4626(eulerAggregationVaultCached).previewRedeem(shares); + } else { + // re-calculate shares in case of socialized loss + shares = IERC4626(eulerAggregationVaultCached).previewWithdraw(assets); + } } if (availableAssets < assets) { @@ -151,6 +159,8 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue IFourSixTwoSixAgg(eulerAggregationVaultCached).executeAggregationVaultWithdraw( caller, receiver, owner, assets, shares ); + + return (assets, shares); } /// @notice Get strategy address from withdrawal queue by index. diff --git a/test/e2e/HooksE2ETest.t.sol b/test/e2e/HooksE2ETest.t.sol index 59bb388b..0d33f8fd 100644 --- a/test/e2e/HooksE2ETest.t.sol +++ b/test/e2e/HooksE2ETest.t.sol @@ -62,7 +62,7 @@ contract HooksE2ETest is AggregationLayerVaultBase { } function testSetHooksConfigWithInvalidHookedFns() public { - uint32 expectedHookedFns = 1 << 5; + uint32 expectedHookedFns = 1 << 6; vm.startPrank(manager); address hooksContract = address(new HooksContract()); vm.expectRevert(ErrorsLib.InvalidHookedFns.selector); From 4a415ddb2c5582e67f9f305d60e296105db676a7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 21 Jun 2024 17:35:51 +0300 Subject: [PATCH 189/316] refactor --- src/AggregationLayerVault.sol | 48 +++++++++++-------- ...oSixAgg.sol => IAggregationLayerVault.sol} | 4 +- src/interface/IWithdrawalQueue.sol | 1 + src/plugin/Rebalancer.sol | 20 ++++---- src/plugin/WithdrawalQueue.sol | 22 ++++++--- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 11 ++--- test/e2e/PerformanceFeeE2ETest.t.sol | 2 +- test/unit/GulpTest.t.sol | 8 ++-- test/unit/HarvestTest.t.sol | 10 ++-- 9 files changed, 69 insertions(+), 57 deletions(-) rename src/interface/{IFourSixTwoSixAgg.sol => IAggregationLayerVault.sol} (91%) diff --git a/src/AggregationLayerVault.sol b/src/AggregationLayerVault.sol index 4544dfb0..9fcbe3b5 100644 --- a/src/AggregationLayerVault.sol +++ b/src/AggregationLayerVault.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; -import {IFourSixTwoSixAgg} from "./interface/IFourSixTwoSixAgg.sol"; +import {IAggregationLayerVault} from "./interface/IAggregationLayerVault.sol"; import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; // contracts import {Dispatch} from "./Dispatch.sol"; @@ -32,7 +32,7 @@ contract AggregationLayerVault is ERC4626Upgradeable, AccessControlEnumerableUpgradeable, Dispatch, - IFourSixTwoSixAgg + IAggregationLayerVault { using SafeERC20 for IERC20; using SafeCast for uint256; @@ -197,23 +197,6 @@ contract AggregationLayerVault is use(MODULE_HOOKS) {} - /// @notice Harvest strategy. - /// @param strategy address of strategy - function harvest(address strategy) external { - _harvest(strategy); - - _gulp(); - } - - /// @notice Harvest multiple strategies. - /// @param _strategies an array of strategy addresses. - function harvestMultipleStrategies(address[] calldata _strategies) external nonReentrant { - for (uint256 i; i < _strategies.length; ++i) { - _harvest(_strategies[i]); - } - _gulp(); - } - function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external nonReentrant @@ -238,6 +221,13 @@ contract AggregationLayerVault is emit EventsLib.Rebalance(_strategy, _amountToRebalance, _isDeposit); } + /// @notice Harvest all the strategies. + function harvest() external nonReentrant { + _updateInterestAccrued(); + + _harvest(); + } + /// @notice update accrued interest function updateInterestAccrued() external { return _updateInterestAccrued(); @@ -375,7 +365,7 @@ contract AggregationLayerVault is } address caller = _msgSender(); - _callHooksTarget(DEPOSIT, caller); + _callHooksTarget(MINT, caller); uint256 assets = previewMint(shares); super._deposit(caller, receiver, assets, shares); @@ -405,6 +395,8 @@ contract AggregationLayerVault is address caller = _msgSender(); _callHooksTarget(WITHDRAW, caller); + _harvest(); + shares = _convertToShares(assets, Math.Rounding.Ceil); AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); @@ -436,6 +428,8 @@ contract AggregationLayerVault is address caller = _msgSender(); _callHooksTarget(REDEEM, caller); + _harvest(); + assets = _convertToAssets(shares, Math.Rounding.Floor); AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); @@ -498,7 +492,17 @@ contract AggregationLayerVault is emit EventsLib.Gulp($.interestLeft, $.interestSmearEnd); } - function _harvest(address _strategy) internal { + function _harvest() internal { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + (address[] memory withdrawalQueueArray, uint256 length) = + IWithdrawalQueue($.withdrawalQueue).getWithdrawalQueueArray(); + for (uint256 i; i < length; ++i) { + _executeHarvest(withdrawalQueueArray[i]); + } + } + + function _executeHarvest(address _strategy) internal { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); uint120 strategyAllocatedAmount = $.strategies[_strategy].allocated; @@ -535,6 +539,8 @@ contract AggregationLayerVault is } } + _gulp(); + emit EventsLib.Harvest(_strategy, underlyingBalance, strategyAllocatedAmount); } diff --git a/src/interface/IFourSixTwoSixAgg.sol b/src/interface/IAggregationLayerVault.sol similarity index 91% rename from src/interface/IFourSixTwoSixAgg.sol rename to src/interface/IAggregationLayerVault.sol index 30300a6f..37ab97c2 100644 --- a/src/interface/IFourSixTwoSixAgg.sol +++ b/src/interface/IAggregationLayerVault.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.0; import {Strategy} from "../lib/StorageLib.sol"; -interface IFourSixTwoSixAgg { +interface IAggregationLayerVault { function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external; function gulp() external; - function harvest(address strategy) external; + function harvest() external; function executeStrategyWithdraw(address _strategy, uint256 _withdrawAmount) external; function executeAggregationVaultWithdraw( address caller, diff --git a/src/interface/IWithdrawalQueue.sol b/src/interface/IWithdrawalQueue.sol index 47143532..47285555 100644 --- a/src/interface/IWithdrawalQueue.sol +++ b/src/interface/IWithdrawalQueue.sol @@ -17,4 +17,5 @@ interface IWithdrawalQueue { function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external; function withdrawalQueueLength() external view returns (uint256); + function getWithdrawalQueueArray() external view returns (address[] memory, uint256); } diff --git a/src/plugin/Rebalancer.sol b/src/plugin/Rebalancer.sol index 86f4c42d..88c97be3 100644 --- a/src/plugin/Rebalancer.sol +++ b/src/plugin/Rebalancer.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; // interfaces -import {IFourSixTwoSixAgg, Strategy} from "../interface/IFourSixTwoSixAgg.sol"; +import {IAggregationLayerVault, Strategy} from "../interface/IAggregationLayerVault.sol"; import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; contract Rebalancer { @@ -18,12 +18,13 @@ contract Rebalancer { /// @param _curatedVault Curated vault address. /// @param _strategies Strategies addresses. function executeRebalance(address _curatedVault, address[] calldata _strategies) external { + IAggregationLayerVault(_curatedVault).gulp(); + for (uint256 i; i < _strategies.length; ++i) { _rebalance(_curatedVault, _strategies[i]); } } - /// @dev This function will first harvest yield, gulps and update interest. /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. /// If current allocation is less than target allocation, the aggregator will: /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash @@ -35,12 +36,10 @@ contract Rebalancer { return; //nothing to rebalance as that's the cash reserve } - IFourSixTwoSixAgg(_curatedVault).harvest(_strategy); - - Strategy memory strategyData = IFourSixTwoSixAgg(_curatedVault).getStrategy(_strategy); + Strategy memory strategyData = IAggregationLayerVault(_curatedVault).getStrategy(_strategy); - uint256 totalAllocationPointsCache = IFourSixTwoSixAgg(_curatedVault).totalAllocationPoints(); - uint256 totalAssetsAllocatableCache = IFourSixTwoSixAgg(_curatedVault).totalAssetsAllocatable(); + uint256 totalAllocationPointsCache = IAggregationLayerVault(_curatedVault).totalAllocationPoints(); + uint256 totalAssetsAllocatableCache = IAggregationLayerVault(_curatedVault).totalAssetsAllocatable(); uint256 targetAllocation = totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; @@ -61,8 +60,9 @@ contract Rebalancer { } else if (strategyData.allocated < targetAllocation) { // Deposit uint256 targetCash = totalAssetsAllocatableCache - * IFourSixTwoSixAgg(_curatedVault).getStrategy(address(0)).allocationPoints / totalAllocationPointsCache; - uint256 currentCash = totalAssetsAllocatableCache - IFourSixTwoSixAgg(_curatedVault).totalAllocated(); + * IAggregationLayerVault(_curatedVault).getStrategy(address(0)).allocationPoints + / totalAllocationPointsCache; + uint256 currentCash = totalAssetsAllocatableCache - IAggregationLayerVault(_curatedVault).totalAllocated(); // Calculate available cash to put in strategies uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; @@ -84,7 +84,7 @@ contract Rebalancer { isDeposit = true; } - IFourSixTwoSixAgg(_curatedVault).rebalance(_strategy, amountToRebalance, isDeposit); + IAggregationLayerVault(_curatedVault).rebalance(_strategy, amountToRebalance, isDeposit); emit ExecuteRebalance(_curatedVault, _strategy, strategyData.allocated, targetAllocation, amountToRebalance); } diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index dd798788..aa0172ed 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; // interfaces import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; -import {IFourSixTwoSixAgg} from "../interface/IFourSixTwoSixAgg.sol"; +import {IAggregationLayerVault} from "../interface/IAggregationLayerVault.sol"; import {IWithdrawalQueue} from "../interface/IWithdrawalQueue.sol"; // contracts import {AccessControlEnumerableUpgradeable} from @@ -117,7 +117,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue ) external returns (uint256, uint256) { _isCallerAggregationVault(); - WithdrawalQueueStorage memory $ = _getWithdrawalQueueStorage(); + WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); address eulerAggregationVaultCached = $.eulerAggregationVault; if (availableAssets < assets) { @@ -125,13 +125,11 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue for (uint256 i; i < numStrategies; ++i) { IERC4626 strategy = IERC4626($.withdrawalQueue[i]); - IFourSixTwoSixAgg(eulerAggregationVaultCached).harvest(address(strategy)); - uint256 underlyingBalance = strategy.maxWithdraw(eulerAggregationVaultCached); uint256 desiredAssets = assets - availableAssets; uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; - IFourSixTwoSixAgg(eulerAggregationVaultCached).executeStrategyWithdraw( + IAggregationLayerVault(eulerAggregationVaultCached).executeStrategyWithdraw( address(strategy), withdrawAmount ); @@ -156,7 +154,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue revert NotEnoughAssets(); } - IFourSixTwoSixAgg(eulerAggregationVaultCached).executeAggregationVaultWithdraw( + IAggregationLayerVault(eulerAggregationVaultCached).executeAggregationVaultWithdraw( caller, receiver, owner, assets, shares ); @@ -172,6 +170,18 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue return $.withdrawalQueue[_index]; } + function getWithdrawalQueueArray() external view returns (address[] memory, uint256) { + WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); + uint256 withdrawalQueueLengthCached = $.withdrawalQueue.length; + + address[] memory withdrawalQueueMem = new address[](withdrawalQueueLengthCached); + for (uint256 i; i < withdrawalQueueLengthCached; ++i) { + withdrawalQueueMem[i] = $.withdrawalQueue[i]; + } + + return (withdrawalQueueMem, withdrawalQueueLengthCached); + } + /// @notice Return the withdrawal queue length. /// @return uint256 length. function withdrawalQueueLength() external pure returns (uint256) { diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 96f902fa..8255eaaf 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -347,7 +347,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // harvest vm.prank(user1); - aggregationLayerVault.harvest(address(eTST)); + aggregationLayerVault.harvest(); vm.warp(block.timestamp + 2 weeks); // full withdraw, will have to withdraw from strategy as cash reserve is not enough @@ -474,11 +474,8 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { } // harvest - address[] memory strategiesToHarvest = new address[](2); - strategiesToHarvest[0] = address(eTST); - strategiesToHarvest[1] = address(eTSTsecondary); vm.prank(user1); - aggregationLayerVault.harvestMultipleStrategies(strategiesToHarvest); + aggregationLayerVault.harvest(); vm.warp(block.timestamp + 2 weeks); // full withdraw, will have to withdraw from strategy as cash reserve is not enough @@ -608,10 +605,8 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { } // harvest - address[] memory strategiesToHarvest = new address[](1); - strategiesToHarvest[0] = address(eTST); vm.prank(user1); - aggregationLayerVault.harvestMultipleStrategies(strategiesToHarvest); + aggregationLayerVault.harvest(); vm.warp(block.timestamp + 2 weeks); vm.prank(manager); diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 1bb5cb55..8a75608e 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -114,7 +114,7 @@ contract PerformanceFeeE2ETest is AggregationLayerVaultBase { // harvest vm.prank(user1); - aggregationLayerVault.harvest(address(eTST)); + aggregationLayerVault.harvest(); assertEq(assetTST.balanceOf(feeRecipient), expectedPerformanceFee); assertEq( diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index f1ac33ff..8ada1f0b 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -83,7 +83,7 @@ contract GulpTest is AggregationLayerVaultBase { eTST.skim(type(uint256).max, address(aggregationLayerVault)); } vm.prank(user1); - aggregationLayerVault.harvest(address(eTST)); + aggregationLayerVault.harvest(); assertEq(aggregationLayerVault.interestAccrued(), 0); @@ -108,7 +108,7 @@ contract GulpTest is AggregationLayerVaultBase { abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); vm.prank(user1); - aggregationLayerVault.harvest(address(eTST)); + aggregationLayerVault.harvest(); } function testGulpAfterNegativeYieldBiggerThanInterestLeft() public { @@ -134,7 +134,7 @@ contract GulpTest is AggregationLayerVaultBase { eTST.skim(type(uint256).max, address(aggregationLayerVault)); } vm.prank(user1); - aggregationLayerVault.harvest(address(eTST)); + aggregationLayerVault.harvest(); assertEq(aggregationLayerVault.interestAccrued(), 0); @@ -159,6 +159,6 @@ contract GulpTest is AggregationLayerVaultBase { abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); vm.prank(user1); - aggregationLayerVault.harvest(address(eTST)); + aggregationLayerVault.harvest(); } } diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index 4687df28..15bc69e1 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -68,7 +68,7 @@ contract HarvestTest is AggregationLayerVaultBase { assertTrue(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))) == strategyBefore.allocated); vm.prank(user1); - aggregationLayerVault.harvest(address(eTST)); + aggregationLayerVault.harvest(); assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated); assertEq(aggregationLayerVault.totalAllocated(), totalAllocatedBefore); @@ -88,7 +88,7 @@ contract HarvestTest is AggregationLayerVaultBase { uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); vm.prank(user1); - aggregationLayerVault.harvest(address(eTST)); + aggregationLayerVault.harvest(); assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedAllocated); assertEq( @@ -113,7 +113,7 @@ contract HarvestTest is AggregationLayerVaultBase { assertTrue(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))) < strategyBefore.allocated); vm.prank(user1); - aggregationLayerVault.harvest(address(eTST)); + aggregationLayerVault.harvest(); } function testHarvestNegativeYieldAndWithdrawSingleUser() public { @@ -139,7 +139,7 @@ contract HarvestTest is AggregationLayerVaultBase { uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - aggregationLayerVault.harvest(address(eTST)); + aggregationLayerVault.harvest(); aggregationLayerVault.redeem(user1SharesBefore, user1, user1); vm.stopPrank(); @@ -185,7 +185,7 @@ contract HarvestTest is AggregationLayerVaultBase { / aggregationLayerVault.totalSupply() - user2SocializedLoss; vm.prank(user1); - aggregationLayerVault.harvest(address(eTST)); + aggregationLayerVault.harvest(); uint256 user1SharesAfter = aggregationLayerVault.balanceOf(user1); uint256 user1AssetsAfter = aggregationLayerVault.convertToAssets(user1SharesAfter); From cf47798a36548596a436a7a9a70e88d703dd50a4 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:52:17 +0300 Subject: [PATCH 190/316] feat: net yield/loss --- src/AggregationLayerVault.sol | 60 +++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/src/AggregationLayerVault.sol b/src/AggregationLayerVault.sol index 9fcbe3b5..0f1776cd 100644 --- a/src/AggregationLayerVault.sol +++ b/src/AggregationLayerVault.sol @@ -497,25 +497,51 @@ contract AggregationLayerVault is (address[] memory withdrawalQueueArray, uint256 length) = IWithdrawalQueue($.withdrawalQueue).getWithdrawalQueueArray(); + + uint256 totalYield; + uint256 totalLoss; for (uint256 i; i < length; ++i) { - _executeHarvest(withdrawalQueueArray[i]); + (uint256 yield, uint256 loss) = _executeHarvest(withdrawalQueueArray[i]); + + totalYield += yield; + totalLoss += loss; + } + + $.totalAllocated = $.totalAllocated + totalYield - totalLoss; + + if (totalLoss > totalYield) { + uint256 netLoss = totalLoss - totalYield; + $.totalAllocated -= netLoss; + + if ($.interestLeft >= netLoss) { + // cut loss from interest left only + $.interestLeft -= uint168(netLoss); + } else { + // cut the interest left and socialize the diff + $.totalAssetsDeposited -= netLoss - $.interestLeft; + $.interestLeft = 0; + } } + + _gulp(); } - function _executeHarvest(address _strategy) internal { + function _executeHarvest(address _strategy) internal returns (uint256, uint256) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); uint120 strategyAllocatedAmount = $.strategies[_strategy].allocated; - if (strategyAllocatedAmount == 0) return; + if (strategyAllocatedAmount == 0) return (0, 0); uint256 underlyingBalance = IERC4626(_strategy).maxWithdraw(address(this)); + uint256 yield; + uint256 loss; if (underlyingBalance == strategyAllocatedAmount) { - return; + return (yield, loss); } else if (underlyingBalance > strategyAllocatedAmount) { // There's yield! - uint256 yield = underlyingBalance - strategyAllocatedAmount; + yield = underlyingBalance - strategyAllocatedAmount; uint120 accruedPerformanceFee = _accruePerformanceFee(_strategy, yield); if (accruedPerformanceFee > 0) { @@ -524,24 +550,26 @@ contract AggregationLayerVault is } $.strategies[_strategy].allocated = uint120(underlyingBalance); - $.totalAllocated += yield; + // $.totalAllocated += yield; } else { - uint256 loss = strategyAllocatedAmount - underlyingBalance; + loss = strategyAllocatedAmount - underlyingBalance; $.strategies[_strategy].allocated = uint120(underlyingBalance); - $.totalAllocated -= loss; - - if ($.interestLeft >= loss) { - $.interestLeft -= uint168(loss); - } else { - $.totalAssetsDeposited -= loss - $.interestLeft; - $.interestLeft = 0; - } + // $.totalAllocated -= loss; + + // if ($.interestLeft >= loss) { + // $.interestLeft -= uint168(loss); + // } else { + // $.totalAssetsDeposited -= loss - $.interestLeft; + // $.interestLeft = 0; + // } } - _gulp(); + // _gulp(); emit EventsLib.Harvest(_strategy, underlyingBalance, strategyAllocatedAmount); + + return (yield, loss); } function _accruePerformanceFee(address _strategy, uint256 _yield) internal returns (uint120) { From 54c513552176ef04de8b4c6d20942bf617773bae Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 24 Jun 2024 13:16:00 +0300 Subject: [PATCH 191/316] refactor --- src/AggregationLayerVault.sol | 149 ++++++++++++++-------------------- 1 file changed, 63 insertions(+), 86 deletions(-) diff --git a/src/AggregationLayerVault.sol b/src/AggregationLayerVault.sol index 0f1776cd..f03dcbc3 100644 --- a/src/AggregationLayerVault.sol +++ b/src/AggregationLayerVault.sol @@ -197,6 +197,11 @@ contract AggregationLayerVault is use(MODULE_HOOKS) {} + /// @notice Rebalance strategy by depositing or withdrawing the amount to rebalance to hit target allocation. + /// @dev Can only be called by an address that hold the REBALANCER role. + /// @param _strategy Strategy address. + /// @param _amountToRebalance Amount to deposit or withdraw. + /// @param _isDeposit bool to indicate if it is a deposit or a withdraw. function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external nonReentrant @@ -222,6 +227,8 @@ contract AggregationLayerVault is } /// @notice Harvest all the strategies. + /// @dev This function will loop through the strategies following the withdrawal queue order and harvest all. + /// @dev Harvest yield and losses will be aggregated and only net yield/loss will be accounted. function harvest() external nonReentrant { _updateInterestAccrued(); @@ -294,6 +301,8 @@ contract AggregationLayerVault is return _interestAccruedFromCache(); } + /// @notice Get saving rate data. + /// @return avsr AggregationVaultSavingRate struct. function getAggregationVaultSavingRate() external view returns (AggregationVaultSavingRate memory) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); AggregationVaultSavingRate memory avsr = AggregationVaultSavingRate({ @@ -337,48 +346,24 @@ contract AggregationLayerVault is } /// @dev See {IERC4626-deposit}. - /// @dev Increate the total assets deposited. - function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256) { - uint256 maxAssets = maxDeposit(receiver); - if (assets > maxAssets) { - revert ERC4626ExceededMaxDeposit(receiver, assets, maxAssets); - } - - address caller = _msgSender(); - _callHooksTarget(DEPOSIT, caller); - - uint256 shares = previewDeposit(assets); - super._deposit(caller, receiver, assets, shares); - - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - $.totalAssetsDeposited += assets; + /// @dev Call DEPOSIT hook if enabled. + function deposit(uint256 _assets, address _receiver) public override nonReentrant returns (uint256) { + _callHooksTarget(DEPOSIT, _msgSender()); - return shares; + return super.deposit(_assets, _receiver); } /// @dev See {IERC4626-mint}. - /// @dev Increate the total assets deposited. - function mint(uint256 shares, address receiver) public override nonReentrant returns (uint256) { - uint256 maxShares = maxMint(receiver); - if (shares > maxShares) { - revert ERC4626ExceededMaxMint(receiver, shares, maxShares); - } - - address caller = _msgSender(); - _callHooksTarget(MINT, caller); - - uint256 assets = previewMint(shares); - super._deposit(caller, receiver, assets, shares); - - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - $.totalAssetsDeposited += assets; + /// @dev Call MINT hook if enabled. + function mint(uint256 _shares, address _receiver) public override nonReentrant returns (uint256) { + _callHooksTarget(MINT, _msgSender()); - return assets; + return super.mint(_shares, _receiver); } /// @dev See {IERC4626-withdraw}. - /// @dev this function update the accrued interest - function withdraw(uint256 assets, address receiver, address owner) + /// @dev this function update the accrued interest and call WITHDRAW hook. + function withdraw(uint256 _assets, address _receiver, address _owner) public override nonReentrant @@ -387,31 +372,16 @@ contract AggregationLayerVault is // Move interest to totalAssetsDeposited _updateInterestAccrued(); - uint256 maxAssets = _convertToAssets(balanceOf(owner), Math.Rounding.Floor); - if (assets > maxAssets) { - revert ERC4626ExceededMaxWithdraw(owner, assets, maxAssets); - } - - address caller = _msgSender(); - _callHooksTarget(WITHDRAW, caller); + _callHooksTarget(WITHDRAW, _msgSender()); _harvest(); - shares = _convertToShares(assets, Math.Rounding.Ceil); - - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); - - (, shares) = IWithdrawalQueue($.withdrawalQueue).callWithdrawalQueue( - caller, receiver, owner, assets, shares, assetsRetrieved, false - ); - - return shares; + return super.withdraw(_assets, _receiver, _owner); } /// @dev See {IERC4626-redeem}. - /// @dev this function update the accrued interest - function redeem(uint256 shares, address receiver, address owner) + /// @dev this function update the accrued interest and call WITHDRAW hook. + function redeem(uint256 _shares, address _receiver, address _owner) public override nonReentrant @@ -420,26 +390,11 @@ contract AggregationLayerVault is // Move interest to totalAssetsDeposited _updateInterestAccrued(); - uint256 maxShares = balanceOf(owner); - if (shares > maxShares) { - revert ERC4626ExceededMaxRedeem(owner, shares, maxShares); - } - - address caller = _msgSender(); - _callHooksTarget(REDEEM, caller); + _callHooksTarget(REDEEM, _msgSender()); _harvest(); - assets = _convertToAssets(shares, Math.Rounding.Floor); - - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); - - (assets,) = IWithdrawalQueue($.withdrawalQueue).callWithdrawalQueue( - caller, receiver, owner, assets, shares, assetsRetrieved, true - ); - - return assets; + return super.redeem(_shares, _receiver, _owner); } /// @notice Return the total amount of assets deposited, plus the accrued interest. @@ -459,6 +414,29 @@ contract AggregationLayerVault is return IERC20(asset()).balanceOf(address(this)) + $.totalAllocated; } + /// @dev See {IERC4626-_deposit}. + /// @dev Increate the total assets deposited. + function _deposit(address _caller, address _receiver, uint256 _assets, uint256 _shares) internal override { + super._deposit(_caller, _receiver, _assets, _shares); + + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + $.totalAssetsDeposited += _assets; + } + + /// @dev See {IERC4626-_withdraw}. + /// @dev This function do not withdraw assets, it makes call to WithdrawalQueue to finish the withdraw request. + function _withdraw(address _caller, address _receiver, address _owner, uint256 _assets, uint256 _shares) + internal + override + { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); + + IWithdrawalQueue($.withdrawalQueue).callWithdrawalQueue( + _caller, _receiver, _owner, _assets, _shares, assetsRetrieved, false + ); + } + /// @notice update accrued interest. function _updateInterestAccrued() internal { uint256 accruedInterest = _interestAccruedFromCache(); @@ -472,7 +450,7 @@ contract AggregationLayerVault is $.totalAssetsDeposited += accruedInterest; } - /// @dev gulp positive yield and increment the left interest + /// @dev gulp positive yield into interest left amd update accrued interest. function _gulp() internal { _updateInterestAccrued(); @@ -492,6 +470,8 @@ contract AggregationLayerVault is emit EventsLib.Gulp($.interestLeft, $.interestSmearEnd); } + /// @dev Loop through stratgies, aggregated yield and lossm and account for net amount. + /// @dev Loss socialization will be taken out from interest left first, if not enough, sozialize on deposits. function _harvest() internal { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); @@ -511,7 +491,6 @@ contract AggregationLayerVault is if (totalLoss > totalYield) { uint256 netLoss = totalLoss - totalYield; - $.totalAllocated -= netLoss; if ($.interestLeft >= netLoss) { // cut loss from interest left only @@ -526,6 +505,11 @@ contract AggregationLayerVault is _gulp(); } + /// TODO: better events + /// @dev Execute harvest on a single strategy. + /// @param _strategy Strategy address. + /// @return yiled Amount of yield if any, else 0. + /// @return loss Amount of loss if any, else 0. function _executeHarvest(address _strategy) internal returns (uint256, uint256) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); @@ -540,7 +524,6 @@ contract AggregationLayerVault is if (underlyingBalance == strategyAllocatedAmount) { return (yield, loss); } else if (underlyingBalance > strategyAllocatedAmount) { - // There's yield! yield = underlyingBalance - strategyAllocatedAmount; uint120 accruedPerformanceFee = _accruePerformanceFee(_strategy, yield); @@ -550,28 +533,20 @@ contract AggregationLayerVault is } $.strategies[_strategy].allocated = uint120(underlyingBalance); - // $.totalAllocated += yield; } else { loss = strategyAllocatedAmount - underlyingBalance; $.strategies[_strategy].allocated = uint120(underlyingBalance); - // $.totalAllocated -= loss; - - // if ($.interestLeft >= loss) { - // $.interestLeft -= uint168(loss); - // } else { - // $.totalAssetsDeposited -= loss - $.interestLeft; - // $.interestLeft = 0; - // } } - - // _gulp(); - emit EventsLib.Harvest(_strategy, underlyingBalance, strategyAllocatedAmount); return (yield, loss); } + /// @dev Accrue performace fee on harvested yield. + /// @param _strategy Strategy that the yield is harvested from. + /// @param _yield Amount of yield harvested. + /// @return feeAssets Amount of performance fee taken. function _accruePerformanceFee(address _strategy, uint256 _yield) internal returns (uint120) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); @@ -613,7 +588,7 @@ contract AggregationLayerVault is } /// @dev Get accrued interest without updating it. - /// @return uint256 accrued interest + /// @return uint256 Accrued interest. function _interestAccruedFromCache() internal view returns (uint256) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); @@ -634,10 +609,12 @@ contract AggregationLayerVault is return $.interestLeft * timePassed / totalDuration; } + /// @dev Override for _msgSender() to be aware of the EVC forwarded calls. function _msgSender() internal view override (ContextUpgradeable, Shared) returns (address) { return Shared._msgSender(); } + /// @dev Check if caller is WithdrawalQueue address, if not revert. function _isCallerWithdrawalQueue() internal view { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); From 2661030849f3060058549164c9c78a45e538e4f0 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 25 Jun 2024 10:43:19 +0300 Subject: [PATCH 192/316] refactor and add tests --- src/AggregationLayerVault.sol | 46 +++--- src/AggregationLayerVaultFactory.sol | 4 +- src/interface/IAggregationLayerVault.sol | 26 ++- src/interface/IWithdrawalQueue.sol | 5 +- src/lib/StorageLib.sol | 17 +- src/module/AllocationPoints.sol | 15 +- src/plugin/Rebalancer.sol | 5 +- src/plugin/WithdrawalQueue.sol | 55 +++--- test/common/AggregationLayerVaultBase.t.sol | 4 +- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 25 +-- test/e2e/HarvestRedeemE2ETest.t.sol | 156 ++++++++++++++++++ test/e2e/PerformanceFeeE2ETest.t.sol | 6 +- test/e2e/StrategyCapE2ETest.t.sol | 8 +- .../fuzz/AdjustAllocationPointsFuzzTest.t.sol | 8 +- test/unit/AdjustAllocationPointsTest.t.sol | 4 +- test/unit/GulpTest.t.sol | 4 +- test/unit/HarvestTest.t.sol | 110 +++++++++--- test/unit/RebalanceTest.t.sol | 15 +- test/unit/RemoveStrategy.t.sol | 6 +- 19 files changed, 377 insertions(+), 142 deletions(-) create mode 100644 test/e2e/HarvestRedeemE2ETest.t.sol diff --git a/src/AggregationLayerVault.sol b/src/AggregationLayerVault.sol index f03dcbc3..2ccacc6c 100644 --- a/src/AggregationLayerVault.sol +++ b/src/AggregationLayerVault.sol @@ -21,10 +21,12 @@ import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgrade import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {StorageLib, AggregationVaultStorage, Strategy} from "./lib/StorageLib.sol"; +import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; import {ErrorsLib} from "./lib/ErrorsLib.sol"; import {EventsLib} from "./lib/EventsLib.sol"; +import {Test, console2, stdError} from "forge-std/Test.sol"; + /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens /// @dev inspired by Yearn v3 ❤️ @@ -65,18 +67,6 @@ contract AggregationLayerVault is uint8 locked; } - struct InitParams { - address evc; - address balanceTracker; - address withdrawalQueuePeriphery; - address rebalancerPerihpery; - address aggregationVaultOwner; - address asset; - string name; - string symbol; - uint256 initialCashAllocationPoints; - } - constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) Dispatch(_rewardsModule, _hooksModule, _feeModule, _allocationPointsModule) {} @@ -90,7 +80,7 @@ contract AggregationLayerVault is AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.locked = REENTRANCYLOCK__UNLOCKED; $.withdrawalQueue = _initParams.withdrawalQueuePeriphery; - $.strategies[address(0)] = Strategy({ + $.strategies[address(0)] = IAggregationLayerVault.Strategy({ allocated: 0, allocationPoints: _initParams.initialCashAllocationPoints.toUint120(), active: true, @@ -209,7 +199,7 @@ contract AggregationLayerVault is { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - Strategy memory strategyData = $.strategies[_strategy]; + IAggregationLayerVault.Strategy memory strategyData = $.strategies[_strategy]; if (_isDeposit) { // Do required approval (safely) and deposit @@ -289,7 +279,7 @@ contract AggregationLayerVault is /// @notice Get strategy params. /// @param _strategy strategy's address /// @return Strategy struct - function getStrategy(address _strategy) external view returns (Strategy memory) { + function getStrategy(address _strategy) external view returns (IAggregationLayerVault.Strategy memory) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.strategies[_strategy]; @@ -433,7 +423,7 @@ contract AggregationLayerVault is uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); IWithdrawalQueue($.withdrawalQueue).callWithdrawalQueue( - _caller, _receiver, _owner, _assets, _shares, assetsRetrieved, false + _caller, _receiver, _owner, _assets, _shares, assetsRetrieved ); } @@ -458,7 +448,7 @@ contract AggregationLayerVault is if ($.totalAssetsDeposited == 0) return; uint256 toGulp = totalAssetsAllocatable() - $.totalAssetsDeposited - $.interestLeft; - + console2.log("toGulp", toGulp); if (toGulp == 0) return; uint256 maxGulp = type(uint168).max - $.interestLeft; @@ -483,23 +473,32 @@ contract AggregationLayerVault is for (uint256 i; i < length; ++i) { (uint256 yield, uint256 loss) = _executeHarvest(withdrawalQueueArray[i]); + console2.log("yield", yield); + console2.log("loss", loss); + totalYield += yield; totalLoss += loss; } $.totalAllocated = $.totalAllocated + totalYield - totalLoss; + console2.log("totalLoss", totalLoss); + console2.log("totalYield", totalYield); + if (totalLoss > totalYield) { uint256 netLoss = totalLoss - totalYield; + uint168 cachedInterestLeft = $.interestLeft; - if ($.interestLeft >= netLoss) { + if (cachedInterestLeft >= netLoss) { + console2.log("cutiing from left interest"); // cut loss from interest left only - $.interestLeft -= uint168(netLoss); + cachedInterestLeft -= uint168(netLoss); } else { // cut the interest left and socialize the diff - $.totalAssetsDeposited -= netLoss - $.interestLeft; - $.interestLeft = 0; + $.totalAssetsDeposited -= netLoss - cachedInterestLeft; + cachedInterestLeft = 0; } + $.interestLeft = cachedInterestLeft; } _gulp(); @@ -519,6 +518,9 @@ contract AggregationLayerVault is uint256 underlyingBalance = IERC4626(_strategy).maxWithdraw(address(this)); + console2.log("underlyingBalance", underlyingBalance); + console2.log("strategyAllocatedAmount", strategyAllocatedAmount); + uint256 yield; uint256 loss; if (underlyingBalance == strategyAllocatedAmount) { diff --git a/src/AggregationLayerVaultFactory.sol b/src/AggregationLayerVaultFactory.sol index 5871eb0d..c7b01441 100644 --- a/src/AggregationLayerVaultFactory.sol +++ b/src/AggregationLayerVaultFactory.sol @@ -6,7 +6,7 @@ import {Rewards} from "./module/Rewards.sol"; import {Hooks} from "./module/Hooks.sol"; import {Fee} from "./module/Fee.sol"; import {WithdrawalQueue} from "./plugin/WithdrawalQueue.sol"; -import {AggregationLayerVault} from "./AggregationLayerVault.sol"; +import {AggregationLayerVault, IAggregationLayerVault} from "./AggregationLayerVault.sol"; // libs import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; @@ -67,7 +67,7 @@ contract AggregationLayerVaultFactory { AggregationLayerVault aggregationLayerVault = new AggregationLayerVault(rewardsModuleAddr, hooksModuleAddr, feeModuleAddr, allocationpointsModuleAddr); - AggregationLayerVault.InitParams memory aggregationVaultInitParams = AggregationLayerVault.InitParams({ + IAggregationLayerVault.InitParams memory aggregationVaultInitParams = IAggregationLayerVault.InitParams({ evc: evc, balanceTracker: balanceTracker, withdrawalQueuePeriphery: address(withdrawalQueue), diff --git a/src/interface/IAggregationLayerVault.sol b/src/interface/IAggregationLayerVault.sol index 37ab97c2..b170d3cd 100644 --- a/src/interface/IAggregationLayerVault.sol +++ b/src/interface/IAggregationLayerVault.sol @@ -1,9 +1,31 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {Strategy} from "../lib/StorageLib.sol"; - interface IAggregationLayerVault { + struct InitParams { + address evc; + address balanceTracker; + address withdrawalQueuePeriphery; + address rebalancerPerihpery; + address aggregationVaultOwner; + address asset; + string name; + string symbol; + uint256 initialCashAllocationPoints; + } + + /// @dev A struct that hold a strategy allocation's config + /// allocated: amount of asset deposited into strategy + /// allocationPoints: number of points allocated to this strategy + /// active: a boolean to indice if this strategy is active or not + /// cap: an optional cap in terms of deposited underlying asset. By default, it is set to 0(not activated) + struct Strategy { + uint120 allocated; + uint120 allocationPoints; + bool active; + uint120 cap; + } + function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external; function gulp() external; function harvest() external; diff --git a/src/interface/IWithdrawalQueue.sol b/src/interface/IWithdrawalQueue.sol index 47285555..d9f378ba 100644 --- a/src/interface/IWithdrawalQueue.sol +++ b/src/interface/IWithdrawalQueue.sol @@ -11,9 +11,8 @@ interface IWithdrawalQueue { address owner, uint256 assets, uint256 shares, - uint256 availableAssets, - bool _isRedeem - ) external returns (uint256, uint256); + uint256 availableAssets + ) external; function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external; function withdrawalQueueLength() external view returns (uint256); diff --git a/src/lib/StorageLib.sol b/src/lib/StorageLib.sol index c5811a8c..71d22215 100644 --- a/src/lib/StorageLib.sol +++ b/src/lib/StorageLib.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +// interfaces +import {IAggregationLayerVault} from "../interface/IAggregationLayerVault.sol"; + /// @custom:storage-location erc7201:euler_aggregation_vault.storage.AggregationVault struct AggregationVaultStorage { /// @dev EVC address @@ -18,7 +21,7 @@ struct AggregationVaultStorage { /// @dev Withdrawal queue contract's address address withdrawalQueue; /// @dev Mapping between strategy address and it's allocation config - mapping(address => Strategy) strategies; + mapping(address => IAggregationLayerVault.Strategy) strategies; /// lastInterestUpdate: last timestamo where interest was updated. @@ -39,18 +42,6 @@ struct AggregationVaultStorage { uint256 hooksConfig; } -/// @dev A struct that hold a strategy allocation's config -/// allocated: amount of asset deposited into strategy -/// allocationPoints: number of points allocated to this strategy -/// active: a boolean to indice if this strategy is active or not -/// cap: an optional cap in terms of deposited underlying asset. By default, it is set to 0(not activated) -struct Strategy { - uint120 allocated; - uint120 allocationPoints; - bool active; - uint120 cap; -} - library StorageLib { // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.AggregationVault")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant AggregationVaultStorageLocation = diff --git a/src/module/AllocationPoints.sol b/src/module/AllocationPoints.sol index 439735b6..ff29556a 100644 --- a/src/module/AllocationPoints.sol +++ b/src/module/AllocationPoints.sol @@ -4,11 +4,12 @@ pragma solidity ^0.8.0; // interfaces import {IERC4626} from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; import {IWithdrawalQueue} from "../interface/IWithdrawalQueue.sol"; +import {IAggregationLayerVault} from "../interface/IAggregationLayerVault.sol"; // contracts import {Shared} from "../Shared.sol"; // libs import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import {StorageLib, AggregationVaultStorage, Strategy} from "../lib/StorageLib.sol"; +import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {ErrorsLib} from "../lib/ErrorsLib.sol"; import {EventsLib} from "../lib/EventsLib.sol"; @@ -22,7 +23,7 @@ abstract contract AllocationPointsModule is Shared { function adjustAllocationPoints(address _strategy, uint256 _newPoints) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - Strategy memory strategyDataCache = $.strategies[_strategy]; + IAggregationLayerVault.Strategy memory strategyDataCache = $.strategies[_strategy]; if (!strategyDataCache.active) { revert ErrorsLib.InactiveStrategy(); @@ -67,8 +68,12 @@ abstract contract AllocationPointsModule is Shared { _callHooksTarget(ADD_STRATEGY, _msgSender()); - $.strategies[_strategy] = - Strategy({allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, cap: 0}); + $.strategies[_strategy] = IAggregationLayerVault.Strategy({ + allocated: 0, + allocationPoints: _allocationPoints.toUint120(), + active: true, + cap: 0 + }); $.totalAllocationPoints += _allocationPoints; IWithdrawalQueue($.withdrawalQueue).addStrategyToWithdrawalQueue(_strategy); @@ -85,7 +90,7 @@ abstract contract AllocationPointsModule is Shared { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - Strategy storage strategyStorage = $.strategies[_strategy]; + IAggregationLayerVault.Strategy storage strategyStorage = $.strategies[_strategy]; if (!strategyStorage.active) { revert ErrorsLib.AlreadyRemoved(); diff --git a/src/plugin/Rebalancer.sol b/src/plugin/Rebalancer.sol index 88c97be3..4d1bbaa6 100644 --- a/src/plugin/Rebalancer.sol +++ b/src/plugin/Rebalancer.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; // interfaces -import {IAggregationLayerVault, Strategy} from "../interface/IAggregationLayerVault.sol"; +import {IAggregationLayerVault} from "../interface/IAggregationLayerVault.sol"; import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; contract Rebalancer { @@ -36,7 +36,8 @@ contract Rebalancer { return; //nothing to rebalance as that's the cash reserve } - Strategy memory strategyData = IAggregationLayerVault(_curatedVault).getStrategy(_strategy); + IAggregationLayerVault.Strategy memory strategyData = + IAggregationLayerVault(_curatedVault).getStrategy(_strategy); uint256 totalAllocationPointsCache = IAggregationLayerVault(_curatedVault).totalAllocationPoints(); uint256 totalAssetsAllocatableCache = IAggregationLayerVault(_curatedVault).totalAssetsAllocatable(); diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index aa0172ed..2fc9db47 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -98,35 +98,32 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue /// @notice Execute the withdraw initiated in the aggregation layer vault. /// @dev Can only be called by the aggregation layer vault's address. - /// @param caller Initiator's address of withdraw. - /// @param receiver Withdraw receiver address. - /// @param owner Shares's owner to burn. - /// @param assets Amount of asset to withdraw. - /// @param shares Amount of shares to burn. - /// @param availableAssets Amount of available asset in aggregation layer vault's cash reserve. - /// @return uint256 The amount of assets withdrawn from the aggregation layer vault. - /// @return uint256 The amount of aggregation layer vault shares burned. + /// @param _caller Initiator's address of withdraw. + /// @param _receiver Withdraw receiver address. + /// @param _owner Shares's owner to burn. + /// @param _assets Amount of asset to withdraw. + /// @param _shares Amount of shares to burn. + /// @param _availableAssets Amount of available asset in aggregation layer vault's cash reserve. function callWithdrawalQueue( - address caller, - address receiver, - address owner, - uint256 assets, - uint256 shares, - uint256 availableAssets, - bool _isRedeem - ) external returns (uint256, uint256) { + address _caller, + address _receiver, + address _owner, + uint256 _assets, + uint256 _shares, + uint256 _availableAssets + ) external { _isCallerAggregationVault(); WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); address eulerAggregationVaultCached = $.eulerAggregationVault; - if (availableAssets < assets) { + if (_availableAssets < _assets) { uint256 numStrategies = $.withdrawalQueue.length; for (uint256 i; i < numStrategies; ++i) { IERC4626 strategy = IERC4626($.withdrawalQueue[i]); uint256 underlyingBalance = strategy.maxWithdraw(eulerAggregationVaultCached); - uint256 desiredAssets = assets - availableAssets; + uint256 desiredAssets = _assets - _availableAssets; uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; IAggregationLayerVault(eulerAggregationVaultCached).executeStrategyWithdraw( @@ -134,31 +131,21 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue ); // update assetsRetrieved - availableAssets += withdrawAmount; + _availableAssets += withdrawAmount; - if (availableAssets >= assets) { + if (_availableAssets >= _assets) { break; } } - - if (_isRedeem) { - // re-calculate assets in case of socialized loss - assets = IERC4626(eulerAggregationVaultCached).previewRedeem(shares); - } else { - // re-calculate shares in case of socialized loss - shares = IERC4626(eulerAggregationVaultCached).previewWithdraw(assets); - } } - if (availableAssets < assets) { + if (_availableAssets < _assets) { revert NotEnoughAssets(); } IAggregationLayerVault(eulerAggregationVaultCached).executeAggregationVaultWithdraw( - caller, receiver, owner, assets, shares + _caller, _receiver, _owner, _assets, _shares ); - - return (assets, shares); } /// @notice Get strategy address from withdrawal queue by index. @@ -184,8 +171,8 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue /// @notice Return the withdrawal queue length. /// @return uint256 length. - function withdrawalQueueLength() external pure returns (uint256) { - WithdrawalQueueStorage memory $ = _getWithdrawalQueueStorage(); + function withdrawalQueueLength() external view returns (uint256) { + WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); return $.withdrawalQueue.length; } diff --git a/test/common/AggregationLayerVaultBase.t.sol b/test/common/AggregationLayerVaultBase.t.sol index 5754364d..4c343a47 100644 --- a/test/common/AggregationLayerVaultBase.t.sol +++ b/test/common/AggregationLayerVaultBase.t.sol @@ -6,7 +6,7 @@ import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; import {IWithdrawalQueue} from "../../src/interface/IWithdrawalQueue.sol"; // contracts import "evk/test/unit/evault/EVaultTestBase.t.sol"; -import {AggregationLayerVault, Strategy} from "../../src/AggregationLayerVault.sol"; +import {AggregationLayerVault, IAggregationLayerVault} from "../../src/AggregationLayerVault.sol"; import {Rebalancer} from "../../src/plugin/Rebalancer.sol"; import {Hooks, HooksModule} from "../../src/module/Hooks.sol"; import {Rewards} from "../../src/module/Rewards.sol"; @@ -92,7 +92,7 @@ contract AggregationLayerVaultBase is EVaultTestBase { } function testInitialParams() public { - Strategy memory cashReserve = aggregationLayerVault.getStrategy(address(0)); + AggregationLayerVault.Strategy memory cashReserve = aggregationLayerVault.getStrategy(address(0)); assertEq(cashReserve.allocated, 0); assertEq(cashReserve.allocationPoints, CASH_RESERVE_ALLOCATION_POINTS); diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 8255eaaf..5c4ae3d8 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -10,7 +10,7 @@ import { IRMTestDefault, TestERC20, WithdrawalQueue, - Strategy + IAggregationLayerVault } from "../common/AggregationLayerVaultBase.t.sol"; contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { @@ -49,7 +49,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); @@ -73,7 +73,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // partial withdraw, no need to withdraw from strategy as cash reserve is enough uint256 amountToWithdraw = 6000e18; { - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); uint256 strategyShareBalanceBefore = eTST.balanceOf(address(aggregationLayerVault)); uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); @@ -131,7 +131,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); @@ -218,8 +218,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - Strategy memory eTSTstrategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - Strategy memory eTSTsecondarystrategyBefore = aggregationLayerVault.getStrategy(address(eTSTsecondary)); + IAggregationLayerVault.Strategy memory eTSTstrategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory eTSTsecondarystrategyBefore = + aggregationLayerVault.getStrategy(address(eTSTsecondary)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), eTSTstrategyBefore.allocated); assertEq( @@ -319,7 +320,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); @@ -411,8 +412,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - Strategy memory eTSTstrategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - Strategy memory eTSTsecondarystrategyBefore = aggregationLayerVault.getStrategy(address(eTSTsecondary)); + IAggregationLayerVault.Strategy memory eTSTstrategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory eTSTsecondarystrategyBefore = + aggregationLayerVault.getStrategy(address(eTSTsecondary)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), eTSTstrategyBefore.allocated); assertEq( @@ -542,8 +544,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - Strategy memory eTSTstrategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - Strategy memory eTSTsecondarystrategyBefore = aggregationLayerVault.getStrategy(address(eTSTsecondary)); + IAggregationLayerVault.Strategy memory eTSTstrategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory eTSTsecondarystrategyBefore = + aggregationLayerVault.getStrategy(address(eTSTsecondary)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), eTSTstrategyBefore.allocated); assertEq( diff --git a/test/e2e/HarvestRedeemE2ETest.t.sol b/test/e2e/HarvestRedeemE2ETest.t.sol new file mode 100644 index 00000000..1a3a43c4 --- /dev/null +++ b/test/e2e/HarvestRedeemE2ETest.t.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + AggregationLayerVaultBase, + AggregationLayerVault, + console2, + EVault, + IAggregationLayerVault +} from "../common/AggregationLayerVaultBase.t.sol"; + +contract HarvestRedeemE2ETest is AggregationLayerVaultBase { + uint256 user1InitialBalance = 100000e18; + uint256 amountToDeposit = 10000e18; + + function setUp() public virtual override { + super.setUp(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + + assetTST.mint(user1, user1InitialBalance); + + // deposit into aggregator + { + uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); + uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(aggregationLayerVault), amountToDeposit); + aggregationLayerVault.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + + uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + + vm.prank(user1); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + + assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); + } + } + + function testHarvestNegativeYieldAndRedeemSingleUser() public { + vm.warp(block.timestamp + 86400); + + // mock a decrease of strategy balance by 10% + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), + abi.encode(aggrCurrentStrategyBalanceAfterNegYield) + ); + + uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + assertTrue(expectedAllocated < strategyBefore.allocated); + + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(aggregationLayerVault)); + + uint256 user1SharesBefore = aggregationLayerVault.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / aggregationLayerVault.totalSupply(); + uint256 expectedUser1Assets = + user1SharesBefore * amountToDeposit / aggregationLayerVault.totalSupply() - user1SocializedLoss; + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + aggregationLayerVault.harvest(); + aggregationLayerVault.redeem(user1SharesBefore, user1, user1); + vm.stopPrank(); + + uint256 user1SharesAfter = aggregationLayerVault.balanceOf(user1); + + assertEq(user1SharesAfter, 0); + assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedUser1Assets, 1); + assertEq(aggregationLayerVault.totalAssetsDeposited(), 0); + } + + function testHarvestNegativeYieldAndRedeemMultipleUser() public { + uint256 user2InitialBalance = 5000e18; + assetTST.mint(user2, user2InitialBalance); + // deposit into aggregator + { + vm.startPrank(user2); + assetTST.approve(address(aggregationLayerVault), user2InitialBalance); + aggregationLayerVault.deposit(user2InitialBalance, user2); + vm.stopPrank(); + } + + vm.warp(block.timestamp + 86400); + + // mock a decrease of strategy balance by 10% + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), + abi.encode(aggrCurrentStrategyBalanceAfterNegYield) + ); + + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + assertTrue(expectedAllocated < strategyBefore.allocated); + + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(aggregationLayerVault)); + uint256 user1SharesBefore = aggregationLayerVault.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / aggregationLayerVault.totalSupply(); + uint256 user2SharesBefore = aggregationLayerVault.balanceOf(user2); + uint256 user2SocializedLoss = user2SharesBefore * negativeYield / aggregationLayerVault.totalSupply(); + + uint256 expectedUser1Assets = user1SharesBefore * (amountToDeposit + user2InitialBalance) + / aggregationLayerVault.totalSupply() - user1SocializedLoss; + uint256 expectedUser2Assets = user2SharesBefore * (amountToDeposit + user2InitialBalance) + / aggregationLayerVault.totalSupply() - user2SocializedLoss; + + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + uint256 user2AssetTSTBalanceBefore = assetTST.balanceOf(user2); + + vm.startPrank(user1); + aggregationLayerVault.harvest(); + aggregationLayerVault.redeem(user1SharesBefore, user1, user1); + vm.stopPrank(); + + vm.prank(user2); + aggregationLayerVault.redeem(user2SharesBefore, user2, user2); + + uint256 user1SharesAfter = aggregationLayerVault.balanceOf(user1); + uint256 user2SharesAfter = aggregationLayerVault.balanceOf(user2); + + assertEq(user1SharesAfter, 0); + assertEq(user2SharesAfter, 0); + assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedUser1Assets, 1); + assertApproxEqAbs(assetTST.balanceOf(user2), user2AssetTSTBalanceBefore + expectedUser2Assets, 1); + assertEq(aggregationLayerVault.totalAssetsDeposited(), 0); + } +} diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 8a75608e..46593036 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -9,7 +9,7 @@ import { IEVault, IRMTestDefault, TestERC20, - Strategy + IAggregationLayerVault } from "../common/AggregationLayerVaultBase.t.sol"; contract PerformanceFeeE2ETest is AggregationLayerVaultBase { @@ -77,7 +77,7 @@ contract PerformanceFeeE2ETest is AggregationLayerVaultBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); @@ -109,7 +109,7 @@ contract PerformanceFeeE2ETest is AggregationLayerVaultBase { (, uint256 performanceFee) = aggregationLayerVault.performanceFeeConfig(); uint256 expectedPerformanceFee = yield * performanceFee / 1e18; - Strategy memory strategyBeforeHarvest = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBeforeHarvest = aggregationLayerVault.getStrategy(address(eTST)); uint256 totalAllocatedBefore = aggregationLayerVault.totalAllocated(); // harvest diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index c5ebcfc6..88bf5b6b 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -9,7 +9,7 @@ import { IEVault, IRMTestDefault, TestERC20, - Strategy, + IAggregationLayerVault, ErrorsLib } from "../common/AggregationLayerVaultBase.t.sol"; @@ -33,7 +33,7 @@ contract StrategyCapE2ETest is AggregationLayerVaultBase { vm.prank(manager); aggregationLayerVault.setStrategyCap(address(eTST), cap); - Strategy memory strategy = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategy = aggregationLayerVault.getStrategy(address(eTST)); assertEq(strategy.cap, cap); } @@ -74,7 +74,7 @@ contract StrategyCapE2ETest is AggregationLayerVaultBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); @@ -135,7 +135,7 @@ contract StrategyCapE2ETest is AggregationLayerVaultBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); diff --git a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol index ceac1837..d363c4db 100644 --- a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol +++ b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol @@ -1,7 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {AggregationLayerVaultBase, AggregationLayerVault, Strategy} from "../common/AggregationLayerVaultBase.t.sol"; +import { + AggregationLayerVaultBase, + AggregationLayerVault, + IAggregationLayerVault +} from "../common/AggregationLayerVaultBase.t.sol"; contract AdjustAllocationsPointsFuzzTest is AggregationLayerVaultBase { function setUp() public virtual override { @@ -21,7 +25,7 @@ contract AdjustAllocationsPointsFuzzTest is AggregationLayerVaultBase { vm.prank(manager); aggregationLayerVault.adjustAllocationPoints(address(eTST), _newAllocationPoints); - Strategy memory strategy = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategy = aggregationLayerVault.getStrategy(address(eTST)); if (_newAllocationPoints < strategyAllocationPoints) { assertEq( diff --git a/test/unit/AdjustAllocationPointsTest.t.sol b/test/unit/AdjustAllocationPointsTest.t.sol index bf440e35..54824568 100644 --- a/test/unit/AdjustAllocationPointsTest.t.sol +++ b/test/unit/AdjustAllocationPointsTest.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import { AggregationLayerVaultBase, AggregationLayerVault, - Strategy, + IAggregationLayerVault, ErrorsLib } from "../common/AggregationLayerVaultBase.t.sol"; @@ -25,7 +25,7 @@ contract AdjustAllocationsPointsTest is AggregationLayerVaultBase { vm.prank(manager); aggregationLayerVault.adjustAllocationPoints(address(eTST), newAllocationPoints); - Strategy memory strategy = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategy = aggregationLayerVault.getStrategy(address(eTST)); assertEq( aggregationLayerVault.totalAllocationPoints(), diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index 8ada1f0b..0924e0f6 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -6,7 +6,7 @@ import { AggregationLayerVault, console2, EVault, - Strategy + IAggregationLayerVault } from "../common/AggregationLayerVaultBase.t.sol"; contract GulpTest is AggregationLayerVaultBase { @@ -42,7 +42,7 @@ contract GulpTest is AggregationLayerVaultBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index 15bc69e1..fa5ae558 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -6,7 +6,7 @@ import { AggregationLayerVault, console2, EVault, - Strategy + IAggregationLayerVault } from "../common/AggregationLayerVaultBase.t.sol"; contract HarvestTest is AggregationLayerVaultBase { @@ -42,7 +42,7 @@ contract HarvestTest is AggregationLayerVaultBase { // rebalance into strategy vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); @@ -60,9 +60,9 @@ contract HarvestTest is AggregationLayerVaultBase { } } - function testHarvest() public { + function testHarvestWithPositiveYield() public { // no yield increase - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); uint256 totalAllocatedBefore = aggregationLayerVault.totalAllocated(); assertTrue(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))) == strategyBefore.allocated); @@ -73,19 +73,17 @@ contract HarvestTest is AggregationLayerVaultBase { assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated); assertEq(aggregationLayerVault.totalAllocated(), totalAllocatedBefore); - // positive yield vm.warp(block.timestamp + 86400); - // mock an increase of strategy balance by 10% uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), abi.encode(aggrCurrentStrategyBalance * 11e17 / 1e18) ); - assertTrue(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))) > strategyBefore.allocated); uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + assertTrue(expectedAllocated > strategyBefore.allocated); vm.prank(user1); aggregationLayerVault.harvest(); @@ -97,26 +95,33 @@ contract HarvestTest is AggregationLayerVaultBase { ); } - function testHarvestNegativeYield() public { + function testHarvestNegativeYieldBiggerThanInterestLeft() public { vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), abi.encode(aggrCurrentStrategyBalance * 9e17 / 1e18) ); - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + + uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + assertTrue(expectedAllocated < strategyBefore.allocated); - assertTrue(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))) < strategyBefore.allocated); + uint256 expectedLoss = strategyBefore.allocated - expectedAllocated; + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); vm.prank(user1); aggregationLayerVault.harvest(); + + // check that loss socialized from the deposits + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedLoss); } - function testHarvestNegativeYieldAndWithdrawSingleUser() public { + function testHarvestNegativeYieldSingleUser() public { vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% @@ -124,12 +129,14 @@ contract HarvestTest is AggregationLayerVaultBase { uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertTrue(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))) < strategyBefore.allocated); + uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + assertTrue(expectedAllocated < strategyBefore.allocated); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(aggregationLayerVault)); uint256 user1SharesBefore = aggregationLayerVault.balanceOf(user1); @@ -140,16 +147,15 @@ contract HarvestTest is AggregationLayerVaultBase { vm.startPrank(user1); aggregationLayerVault.harvest(); - aggregationLayerVault.redeem(user1SharesBefore, user1, user1); vm.stopPrank(); uint256 user1SharesAfter = aggregationLayerVault.balanceOf(user1); + uint256 user1AssetsAfter = aggregationLayerVault.convertToAssets(user1SharesAfter); - assertEq(user1SharesAfter, 0); - assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedUser1Assets, 1); + assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets, 1); } - function testHarvestNegativeYieldwMultipleUser() public { + function testHarvestNegativeYieldMultipleUser() public { uint256 user2InitialBalance = 5000e18; assetTST.mint(user2, user2InitialBalance); // deposit into aggregator @@ -167,12 +173,14 @@ contract HarvestTest is AggregationLayerVaultBase { uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - assertTrue(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))) < strategyBefore.allocated); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + assertTrue(expectedAllocated < strategyBefore.allocated); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(aggregationLayerVault)); uint256 user1SharesBefore = aggregationLayerVault.balanceOf(user1); uint256 user1SocializedLoss = user1SharesBefore * negativeYield / aggregationLayerVault.totalSupply(); @@ -195,4 +203,60 @@ contract HarvestTest is AggregationLayerVaultBase { assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets, 1); assertApproxEqAbs(user2AssetsAfter, expectedUser2Assets, 1); } + + function testHarvestWhenInteresetLeftGreaterThanLoss() public { + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + uint256 totalAllocatedBefore = aggregationLayerVault.totalAllocated(); + + vm.warp(block.timestamp + 86400); + // mock an increase of strategy balance by 10% + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), + abi.encode(aggrCurrentStrategyBalance * 11e17 / 1e18) + ); + + uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + assertTrue(expectedAllocated > strategyBefore.allocated); + + vm.prank(user1); + aggregationLayerVault.harvest(); + + assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedAllocated); + assertEq( + aggregationLayerVault.totalAllocated(), + totalAllocatedBefore + (expectedAllocated - strategyBefore.allocated) + ); + + vm.warp(block.timestamp + 86400); + + // mock a decrease of strategy balance by 2% + uint256 aggrCurrentStrategyBalanceAfterNegYield = expectedAllocated * 98e16 / 1e18; + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), + abi.encode(aggrCurrentStrategyBalanceAfterNegYield) + ); + + strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + assertTrue(expectedAllocated < strategyBefore.allocated); + + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(aggregationLayerVault)); + uint256 user1SharesBefore = aggregationLayerVault.balanceOf(user1); + uint256 expectedUser1Assets = user1SharesBefore * amountToDeposit / aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 interestToBeAccrued = aggregationLayerVault.interestAccrued(); + + vm.startPrank(user1); + aggregationLayerVault.harvest(); + vm.stopPrank(); + + uint256 user1SharesAfter = aggregationLayerVault.balanceOf(user1); + uint256 user1AssetsAfter = aggregationLayerVault.convertToAssets(user1SharesAfter); + + assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets + interestToBeAccrued, 1); + assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + interestToBeAccrued); + } } diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index ca0f69fb..4abbfe55 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -9,7 +9,7 @@ import { IEVault, IRMTestDefault, TestERC20, - Strategy + IAggregationLayerVault } from "../common/AggregationLayerVaultBase.t.sol"; contract RebalanceTest is AggregationLayerVaultBase { @@ -47,7 +47,7 @@ contract RebalanceTest is AggregationLayerVaultBase { // rebalance into strategy vm.warp(block.timestamp + 86400); - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); @@ -90,7 +90,7 @@ contract RebalanceTest is AggregationLayerVaultBase { // rebalance into strategy vm.warp(block.timestamp + 86400); - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); @@ -158,7 +158,8 @@ contract RebalanceTest is AggregationLayerVaultBase { // rebalance into eTSTsecondary vm.warp(block.timestamp + 86400); { - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTSTsecondary)); + IAggregationLayerVault.Strategy memory strategyBefore = + aggregationLayerVault.getStrategy(address(eTSTsecondary)); assertEq( eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), @@ -212,7 +213,7 @@ contract RebalanceTest is AggregationLayerVaultBase { // rebalance into strategy vm.warp(block.timestamp + 86400); - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); @@ -267,7 +268,7 @@ contract RebalanceTest is AggregationLayerVaultBase { vm.warp(block.timestamp + 86400); - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); @@ -321,7 +322,7 @@ contract RebalanceTest is AggregationLayerVaultBase { vm.warp(block.timestamp + 86400); - Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index 3846ccd2..e92cb8c8 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -5,7 +5,7 @@ import { AggregationLayerVaultBase, AggregationLayerVault, IEVault, - Strategy + IAggregationLayerVault } from "../common/AggregationLayerVaultBase.t.sol"; contract RemoveStrategyTest is AggregationLayerVaultBase { @@ -27,7 +27,7 @@ contract RemoveStrategyTest is AggregationLayerVaultBase { vm.prank(manager); aggregationLayerVault.removeStrategy(address(eTST)); - Strategy memory strategyAfter = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyAfter = aggregationLayerVault.getStrategy(address(eTST)); assertEq(strategyAfter.active, false); assertEq(strategyAfter.allocationPoints, 0); @@ -47,7 +47,7 @@ contract RemoveStrategyTest is AggregationLayerVaultBase { vm.prank(manager); aggregationLayerVault.removeStrategy(address(eTST)); - Strategy memory strategyAfter = aggregationLayerVault.getStrategy(address(eTST)); + IAggregationLayerVault.Strategy memory strategyAfter = aggregationLayerVault.getStrategy(address(eTST)); assertEq(strategyAfter.active, false); assertEq(strategyAfter.allocationPoints, 0); From 00c75b0b950da9999bbe9db598d9a47585eb5459 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:02:26 +0300 Subject: [PATCH 193/316] clean --- src/AggregationLayerVault.sol | 18 +++++------------- src/AggregationLayerVaultFactory.sol | 15 +++++++++++++++ src/Dispatch.sol | 5 +++++ src/Shared.sol | 3 +++ src/interface/IAggregationLayerVault.sol | 14 ++++++++++++++ src/lib/StorageLib.sol | 20 +++++++++++--------- src/module/AllocationPoints.sol | 3 +++ src/module/Fee.sol | 3 +++ src/module/Hooks.sol | 3 +++ src/module/Rewards.sol | 2 +- src/plugin/Rebalancer.sol | 5 +++++ src/plugin/WithdrawalQueue.sol | 9 +++++++++ 12 files changed, 77 insertions(+), 23 deletions(-) diff --git a/src/AggregationLayerVault.sol b/src/AggregationLayerVault.sol index 2ccacc6c..716f5ccd 100644 --- a/src/AggregationLayerVault.sol +++ b/src/AggregationLayerVault.sol @@ -27,6 +27,9 @@ import {EventsLib} from "./lib/EventsLib.sol"; import {Test, console2, stdError} from "forge-std/Test.sol"; +/// @title AggregationLayerVault contract +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens /// @dev inspired by Yearn v3 ❤️ @@ -54,23 +57,12 @@ contract AggregationLayerVault is /// @dev interest rate smearing period uint256 public constant INTEREST_SMEAR = 2 weeks; - /// @dev Euler saving rate struct - /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol - /// lastInterestUpdate: last timestamo where interest was updated. - /// interestSmearEnd: timestamp when the smearing of interest end. - /// interestLeft: amount of interest left to smear. - /// locked: if locked or not for update. - struct AggregationVaultSavingRate { - uint40 lastInterestUpdate; - uint40 interestSmearEnd; - uint168 interestLeft; - uint8 locked; - } - constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) Dispatch(_rewardsModule, _hooksModule, _feeModule, _allocationPointsModule) {} + /// @notice Initialize the AggregationLayerVault. + /// @param _initParams InitParams struct. function init(InitParams calldata _initParams) external initializer { __ERC4626_init_unchained(IERC20(_initParams.asset)); __ERC20_init_unchained(_initParams.name, _initParams.symbol); diff --git a/src/AggregationLayerVaultFactory.sol b/src/AggregationLayerVaultFactory.sol index c7b01441..4167ef58 100644 --- a/src/AggregationLayerVaultFactory.sol +++ b/src/AggregationLayerVaultFactory.sol @@ -10,6 +10,9 @@ import {AggregationLayerVault, IAggregationLayerVault} from "./AggregationLayerV // libs import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +/// @title AggregationLayerVaultFactory contract +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) contract AggregationLayerVaultFactory { /// core dependencies address public immutable evc; @@ -25,6 +28,7 @@ contract AggregationLayerVaultFactory { /// @dev WithdrawalQueue plugin implementation address, need to be deployed per aggregation vault address public immutable withdrawalQueueImpl; + /// @dev Init params struct. struct FactoryParams { address evc; address balanceTracker; @@ -36,6 +40,8 @@ contract AggregationLayerVaultFactory { address withdrawalQueueImpl; } + /// @notice Constructor. + /// @param _factoryParams FactoryParams struct. constructor(FactoryParams memory _factoryParams) { evc = _factoryParams.evc; balanceTracker = _factoryParams.balanceTracker; @@ -48,6 +54,15 @@ contract AggregationLayerVaultFactory { withdrawalQueueImpl = _factoryParams.withdrawalQueueImpl; } + /// @notice Deploy a new aggregation layer vault. + /// @dev This will clone a new WithdrawalQueue plugin instance for the aggregation layer vault. + /// @dev This will use the defaut Rebalancer plugin configured in this factory. + /// @dev Both plugins are possible to change by the aggregation layer vault manager. + /// @param _asset Aggreation layer vault' asset address. + /// @param _name Vaut name. + /// @param _symbol Vault symbol. + /// @param _initialCashAllocationPoints The amount of points to initally allocate for cash reserve. + /// @return aggregationLayerVault The address of the new deployed aggregation layer vault. function deployEulerAggregationLayer( address _asset, string memory _name, diff --git a/src/Dispatch.sol b/src/Dispatch.sol index 465c43d9..9d94fe20 100644 --- a/src/Dispatch.sol +++ b/src/Dispatch.sol @@ -6,6 +6,11 @@ import {Shared} from "./Shared.sol"; import {HooksModule} from "./module/Hooks.sol"; import {RewardsModule} from "./module/Rewards.sol"; +/// @title Dispatch contract +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @dev This contract implement the modifier to use for forwarding calls to a specific module using delegateCall. +/// @dev Copied from https://github.com/euler-xyz/euler-vault-kit/blob/55d1a1fd7d572372f1c8b9f58aba0604bda3ca4f/src/EVault/Dispatch.sol. abstract contract Dispatch is RewardsModule, HooksModule { address public immutable MODULE_REWARDS; address public immutable MODULE_HOOKS; diff --git a/src/Shared.sol b/src/Shared.sol index 8054e196..e788987a 100644 --- a/src/Shared.sol +++ b/src/Shared.sol @@ -9,6 +9,9 @@ import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; import {ErrorsLib} from "./lib/ErrorsLib.sol"; import {HooksLib} from "./lib/HooksLib.sol"; +/// @title Shared contract +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) contract Shared { using HooksLib for uint32; diff --git a/src/interface/IAggregationLayerVault.sol b/src/interface/IAggregationLayerVault.sol index b170d3cd..9d37ae7d 100644 --- a/src/interface/IAggregationLayerVault.sol +++ b/src/interface/IAggregationLayerVault.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; interface IAggregationLayerVault { + /// @dev Struct to pass init() call params. struct InitParams { address evc; address balanceTracker; @@ -26,6 +27,19 @@ interface IAggregationLayerVault { uint120 cap; } + /// @dev Euler saving rate struct + /// @dev Based on https://github.com/euler-xyz/euler-vault-kit/blob/master/src/Synths/EulerSavingsRate.sol + /// lastInterestUpdate: last timestamo where interest was updated. + /// interestSmearEnd: timestamp when the smearing of interest end. + /// interestLeft: amount of interest left to smear. + /// locked: if locked or not for update. + struct AggregationVaultSavingRate { + uint40 lastInterestUpdate; + uint40 interestSmearEnd; + uint168 interestLeft; + uint8 locked; + } + function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external; function gulp() external; function harvest() external; diff --git a/src/lib/StorageLib.sol b/src/lib/StorageLib.sol index 71d22215..8b4f3907 100644 --- a/src/lib/StorageLib.sol +++ b/src/lib/StorageLib.sol @@ -6,21 +6,21 @@ import {IAggregationLayerVault} from "../interface/IAggregationLayerVault.sol"; /// @custom:storage-location erc7201:euler_aggregation_vault.storage.AggregationVault struct AggregationVaultStorage { - /// @dev EVC address + /// EVC address address evc; - /// @dev Total amount of _asset deposited into AggregationLayerVault contract + /// Total amount of _asset deposited into AggregationLayerVault contract uint256 totalAssetsDeposited; - /// @dev Total amount of _asset deposited across all strategies. + /// Total amount of _asset deposited across all strategies. uint256 totalAllocated; - /// @dev Total amount of allocation points across all strategies including the cash reserve. + /// Total amount of allocation points across all strategies including the cash reserve. uint256 totalAllocationPoints; - /// @dev fee rate + /// fee rate uint256 performanceFee; - /// @dev fee recipient address + /// fee recipient address address feeRecipient; - /// @dev Withdrawal queue contract's address + /// Withdrawal queue contract's address address withdrawalQueue; - /// @dev Mapping between strategy address and it's allocation config + /// Mapping between strategy address and it's allocation config mapping(address => IAggregationLayerVault.Strategy) strategies; @@ -33,8 +33,9 @@ struct AggregationVaultStorage { /// locked: if locked or not for update. uint8 locked; - + /// Address of balance tracker contract for reward streams integration. address balanceTracker; + /// A mapping to check if a user address enabled balance forwarding for reward streams integration. mapping(address => bool) isBalanceForwarderEnabled; @@ -47,6 +48,7 @@ library StorageLib { bytes32 private constant AggregationVaultStorageLocation = 0x7da5ece5aff94f3377324d715703a012d3253da37511270103c646f171c0aa00; + /// @dev A function to return a pointer for the AggregationVaultStorageLocation. function _getAggregationVaultStorage() internal pure returns (AggregationVaultStorage storage $) { assembly { $.slot := AggregationVaultStorageLocation diff --git a/src/module/AllocationPoints.sol b/src/module/AllocationPoints.sol index ff29556a..0d910687 100644 --- a/src/module/AllocationPoints.sol +++ b/src/module/AllocationPoints.sol @@ -13,6 +13,9 @@ import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {ErrorsLib} from "../lib/ErrorsLib.sol"; import {EventsLib} from "../lib/EventsLib.sol"; +/// @title FeeModule contract +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) abstract contract AllocationPointsModule is Shared { using SafeCast for uint256; diff --git a/src/module/Fee.sol b/src/module/Fee.sol index c1436cdf..67404baf 100644 --- a/src/module/Fee.sol +++ b/src/module/Fee.sol @@ -13,6 +13,9 @@ import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {ErrorsLib} from "../lib/ErrorsLib.sol"; import {EventsLib} from "../lib/EventsLib.sol"; +/// @title FeeModule contract +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) abstract contract FeeModule is Shared { /// @dev The maximum performanceFee the vault can have is 50% uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; diff --git a/src/module/Hooks.sol b/src/module/Hooks.sol index 3c799d2d..adf293fb 100644 --- a/src/module/Hooks.sol +++ b/src/module/Hooks.sol @@ -10,6 +10,9 @@ import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {HooksLib} from "../lib/HooksLib.sol"; import {EventsLib} from "../lib/EventsLib.sol"; +/// @title HooksModule contract +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) abstract contract HooksModule is Shared { using HooksLib for uint32; diff --git a/src/module/Rewards.sol b/src/module/Rewards.sol index 69c785ac..722c0c54 100644 --- a/src/module/Rewards.sol +++ b/src/module/Rewards.sol @@ -16,7 +16,7 @@ import {EventsLib} from "../lib/EventsLib.sol"; /// @title Rewards module /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) -/// @notice A module to provide balancer tracking for reward streats and to integrate with strategies rewards. +/// @notice A module to provide balancer tracking for reward streams and to integrate with strategies rewards. /// @dev See https://github.com/euler-xyz/reward-streams. abstract contract RewardsModule is IBalanceForwarder, Shared { /// @notice Opt in to strategy rewards diff --git a/src/plugin/Rebalancer.sol b/src/plugin/Rebalancer.sol index 4d1bbaa6..302de564 100644 --- a/src/plugin/Rebalancer.sol +++ b/src/plugin/Rebalancer.sol @@ -5,6 +5,11 @@ pragma solidity ^0.8.0; import {IAggregationLayerVault} from "../interface/IAggregationLayerVault.sol"; import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +/// @title Rebalancer plugin +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice A contract to execute rebalance() on the AggregationLayerVault. +/// @dev Usually this contract will hold the `REBALANCER` role. contract Rebalancer { event ExecuteRebalance( address indexed curatedVault, diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index 2fc9db47..118b63b4 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -9,6 +9,12 @@ import {IWithdrawalQueue} from "../interface/IWithdrawalQueue.sol"; import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; +/// @title WithdrawalQueue plugin +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice This contract manage the withdrawalQueue aray(add/remove strategy to the queue, re-order queue). +/// Also it handles finishing the withdraw execution flow through the `callWithdrawalQueue()` function +/// that will be called by the AggregationLayerVault. contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue { error OutOfBounds(); error SameIndexes(); @@ -157,6 +163,9 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue return $.withdrawalQueue[_index]; } + /// @notice Get the withdrawal queue array and it's length. + /// @return withdrawalQueueMem The withdrawal queue array in memory. + /// @return withdrawalQueueLengthCached An uint256 which is the length of the array. function getWithdrawalQueueArray() external view returns (address[] memory, uint256) { WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); uint256 withdrawalQueueLengthCached = $.withdrawalQueue.length; From 59e8da566402e7d03aed2d3231096bdbc6edeb12 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:04:52 +0300 Subject: [PATCH 194/316] clean --- src/AggregationLayerVault.sol | 15 +-------------- test/unit/HarvestTest.t.sol | 1 - 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/AggregationLayerVault.sol b/src/AggregationLayerVault.sol index 716f5ccd..823a8940 100644 --- a/src/AggregationLayerVault.sol +++ b/src/AggregationLayerVault.sol @@ -25,8 +25,6 @@ import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; import {ErrorsLib} from "./lib/ErrorsLib.sol"; import {EventsLib} from "./lib/EventsLib.sol"; -import {Test, console2, stdError} from "forge-std/Test.sol"; - /// @title AggregationLayerVault contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) @@ -439,8 +437,8 @@ contract AggregationLayerVault is AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); if ($.totalAssetsDeposited == 0) return; + uint256 toGulp = totalAssetsAllocatable() - $.totalAssetsDeposited - $.interestLeft; - console2.log("toGulp", toGulp); if (toGulp == 0) return; uint256 maxGulp = type(uint168).max - $.interestLeft; @@ -465,24 +463,17 @@ contract AggregationLayerVault is for (uint256 i; i < length; ++i) { (uint256 yield, uint256 loss) = _executeHarvest(withdrawalQueueArray[i]); - console2.log("yield", yield); - console2.log("loss", loss); - totalYield += yield; totalLoss += loss; } $.totalAllocated = $.totalAllocated + totalYield - totalLoss; - console2.log("totalLoss", totalLoss); - console2.log("totalYield", totalYield); - if (totalLoss > totalYield) { uint256 netLoss = totalLoss - totalYield; uint168 cachedInterestLeft = $.interestLeft; if (cachedInterestLeft >= netLoss) { - console2.log("cutiing from left interest"); // cut loss from interest left only cachedInterestLeft -= uint168(netLoss); } else { @@ -509,10 +500,6 @@ contract AggregationLayerVault is if (strategyAllocatedAmount == 0) return (0, 0); uint256 underlyingBalance = IERC4626(_strategy).maxWithdraw(address(this)); - - console2.log("underlyingBalance", underlyingBalance); - console2.log("strategyAllocatedAmount", strategyAllocatedAmount); - uint256 yield; uint256 loss; if (underlyingBalance == strategyAllocatedAmount) { diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index fa5ae558..34803cab 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -243,7 +243,6 @@ contract HarvestTest is AggregationLayerVaultBase { expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); assertTrue(expectedAllocated < strategyBefore.allocated); - uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(aggregationLayerVault)); uint256 user1SharesBefore = aggregationLayerVault.balanceOf(user1); uint256 expectedUser1Assets = user1SharesBefore * amountToDeposit / aggregationLayerVault.totalSupply(); uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); From 854f5e23418554c611d01ef07926f4aaa85a0aba Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:01:00 +0300 Subject: [PATCH 195/316] clean --- src/AggregationLayerVault.sol | 18 ++++++++++-------- src/Shared.sol | 12 ++++++------ src/lib/EventsLib.sol | 3 ++- src/module/AllocationPoints.sol | 24 ++++++++++++------------ src/module/Fee.sol | 16 ++++++++-------- src/module/Hooks.sol | 4 ++-- src/module/Rewards.sol | 30 +++++++++++++++--------------- 7 files changed, 55 insertions(+), 52 deletions(-) diff --git a/src/AggregationLayerVault.sol b/src/AggregationLayerVault.sol index 823a8940..6277ad95 100644 --- a/src/AggregationLayerVault.sol +++ b/src/AggregationLayerVault.sol @@ -22,8 +22,8 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; -import {ErrorsLib} from "./lib/ErrorsLib.sol"; -import {EventsLib} from "./lib/EventsLib.sol"; +import {ErrorsLib as Errors} from "./lib/ErrorsLib.sol"; +import {EventsLib as Events} from "./lib/EventsLib.sol"; /// @title AggregationLayerVault contract /// @custom:security-contact security@euler.xyz @@ -65,7 +65,7 @@ contract AggregationLayerVault is __ERC4626_init_unchained(IERC20(_initParams.asset)); __ERC20_init_unchained(_initParams.name, _initParams.symbol); - if (_initParams.initialCashAllocationPoints == 0) revert ErrorsLib.InitialAllocationPointsZero(); + if (_initParams.initialCashAllocationPoints == 0) revert Errors.InitialAllocationPointsZero(); AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.locked = REENTRANCYLOCK__UNLOCKED; @@ -203,7 +203,7 @@ contract AggregationLayerVault is $.totalAllocated -= _amountToRebalance; } - emit EventsLib.Rebalance(_strategy, _amountToRebalance, _isDeposit); + emit Events.Rebalance(_strategy, _amountToRebalance, _isDeposit); } /// @notice Harvest all the strategies. @@ -447,7 +447,7 @@ contract AggregationLayerVault is $.interestSmearEnd = uint40(block.timestamp + INTEREST_SMEAR); $.interestLeft += uint168(toGulp); // toGulp <= maxGulp <= max uint168 - emit EventsLib.Gulp($.interestLeft, $.interestSmearEnd); + emit Events.Gulp($.interestLeft, $.interestSmearEnd); } /// @dev Loop through stratgies, aggregated yield and lossm and account for net amount. @@ -484,6 +484,8 @@ contract AggregationLayerVault is $.interestLeft = cachedInterestLeft; } + emit Events.Harvest($.totalAllocated, totalYield, totalLoss); + _gulp(); } @@ -519,7 +521,7 @@ contract AggregationLayerVault is $.strategies[_strategy].allocated = uint120(underlyingBalance); } - emit EventsLib.Harvest(_strategy, underlyingBalance, strategyAllocatedAmount); + emit Events.ExecuteHarvest(_strategy, underlyingBalance, strategyAllocatedAmount); return (yield, loss); } @@ -543,7 +545,7 @@ contract AggregationLayerVault is IERC4626(_strategy).withdraw(feeAssets, cachedFeeRecipient, address(this)); } - emit EventsLib.AccruePerformanceFee(cachedFeeRecipient, _yield, feeAssets); + emit Events.AccruePerformanceFee(cachedFeeRecipient, _yield, feeAssets); return feeAssets.toUint120(); } @@ -599,6 +601,6 @@ contract AggregationLayerVault is function _isCallerWithdrawalQueue() internal view { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - if (_msgSender() != $.withdrawalQueue) revert ErrorsLib.NotWithdrawaQueue(); + if (_msgSender() != $.withdrawalQueue) revert Errors.NotWithdrawaQueue(); } } diff --git a/src/Shared.sol b/src/Shared.sol index e788987a..22a2411a 100644 --- a/src/Shared.sol +++ b/src/Shared.sol @@ -6,8 +6,8 @@ import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; // libs import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; -import {ErrorsLib} from "./lib/ErrorsLib.sol"; import {HooksLib} from "./lib/HooksLib.sol"; +import {ErrorsLib as Errors} from "./lib/ErrorsLib.sol"; /// @title Shared contract /// @custom:security-contact security@euler.xyz @@ -31,7 +31,7 @@ contract Shared { modifier nonReentrant() { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - if ($.locked == REENTRANCYLOCK__LOCKED) revert ErrorsLib.Reentrancy(); + if ($.locked == REENTRANCYLOCK__LOCKED) revert Errors.Reentrancy(); $.locked = REENTRANCYLOCK__LOCKED; _; @@ -52,12 +52,12 @@ contract Shared { function _setHooksConfig(address _hooksTarget, uint32 _hookedFns) internal { if (_hooksTarget != address(0) && IHookTarget(_hooksTarget).isHookTarget() != IHookTarget.isHookTarget.selector) { - revert ErrorsLib.NotHooksContract(); + revert Errors.NotHooksContract(); } if (_hookedFns != 0 && _hooksTarget == address(0)) { - revert ErrorsLib.InvalidHooksTarget(); + revert Errors.InvalidHooksTarget(); } - if (_hookedFns >= ACTIONS_COUNTER) revert ErrorsLib.InvalidHookedFns(); + if (_hookedFns >= ACTIONS_COUNTER) revert Errors.InvalidHookedFns(); AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.hooksConfig = (uint256(uint160(_hooksTarget)) << 32) | uint256(_hookedFns); @@ -94,6 +94,6 @@ contract Shared { } } - revert ErrorsLib.EmptyError(); + revert Errors.EmptyError(); } } diff --git a/src/lib/EventsLib.sol b/src/lib/EventsLib.sol index f2bd3bfe..199e9707 100644 --- a/src/lib/EventsLib.sol +++ b/src/lib/EventsLib.sol @@ -4,9 +4,10 @@ pragma solidity ^0.8.0; library EventsLib { /// @dev AggregationLayerVault events event Gulp(uint256 interestLeft, uint256 interestSmearEnd); - event Harvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); event AccruePerformanceFee(address indexed feeRecipient, uint256 yield, uint256 feeAssets); event Rebalance(address indexed strategy, uint256 amountToRebalance, bool isDeposit); + event ExecuteHarvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); + event Harvest(uint256 totalAllocated, uint256 totlaYield, uint256 totalLoss); /// @dev Allocationpoints events event AdjustAllocationPoints(address indexed strategy, uint256 oldPoints, uint256 newPoints); diff --git a/src/module/AllocationPoints.sol b/src/module/AllocationPoints.sol index 0d910687..c88baca4 100644 --- a/src/module/AllocationPoints.sol +++ b/src/module/AllocationPoints.sol @@ -10,8 +10,8 @@ import {Shared} from "../Shared.sol"; // libs import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; -import {ErrorsLib} from "../lib/ErrorsLib.sol"; -import {EventsLib} from "../lib/EventsLib.sol"; +import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; +import {EventsLib as Events} from "../lib/EventsLib.sol"; /// @title FeeModule contract /// @custom:security-contact security@euler.xyz @@ -29,13 +29,13 @@ abstract contract AllocationPointsModule is Shared { IAggregationLayerVault.Strategy memory strategyDataCache = $.strategies[_strategy]; if (!strategyDataCache.active) { - revert ErrorsLib.InactiveStrategy(); + revert Errors.InactiveStrategy(); } $.strategies[_strategy].allocationPoints = _newPoints.toUint120(); $.totalAllocationPoints = $.totalAllocationPoints + _newPoints - strategyDataCache.allocationPoints; - emit EventsLib.AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); + emit Events.AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); } /// @notice Set cap on strategy allocated amount. @@ -46,12 +46,12 @@ abstract contract AllocationPointsModule is Shared { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); if (!$.strategies[_strategy].active) { - revert ErrorsLib.InactiveStrategy(); + revert Errors.InactiveStrategy(); } $.strategies[_strategy].cap = _cap.toUint120(); - emit EventsLib.SetStrategyCap(_strategy, _cap); + emit Events.SetStrategyCap(_strategy, _cap); } /// @notice Add new strategy with it's allocation points. @@ -62,11 +62,11 @@ abstract contract AllocationPointsModule is Shared { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); if ($.strategies[_strategy].active) { - revert ErrorsLib.StrategyAlreadyExist(); + revert Errors.StrategyAlreadyExist(); } if (IERC4626(_strategy).asset() != IERC4626(address(this)).asset()) { - revert ErrorsLib.InvalidStrategyAsset(); + revert Errors.InvalidStrategyAsset(); } _callHooksTarget(ADD_STRATEGY, _msgSender()); @@ -81,7 +81,7 @@ abstract contract AllocationPointsModule is Shared { $.totalAllocationPoints += _allocationPoints; IWithdrawalQueue($.withdrawalQueue).addStrategyToWithdrawalQueue(_strategy); - emit EventsLib.AddStrategy(_strategy, _allocationPoints); + emit Events.AddStrategy(_strategy, _allocationPoints); } /// @notice Remove strategy and set its allocation points to zero. @@ -89,14 +89,14 @@ abstract contract AllocationPointsModule is Shared { /// @dev Can only be called by an address that have the STRATEGY_REMOVER /// @param _strategy Address of the strategy function removeStrategy(address _strategy) external virtual nonReentrant { - if (_strategy == address(0)) revert ErrorsLib.CanNotRemoveCashReserve(); + if (_strategy == address(0)) revert Errors.CanNotRemoveCashReserve(); AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); IAggregationLayerVault.Strategy storage strategyStorage = $.strategies[_strategy]; if (!strategyStorage.active) { - revert ErrorsLib.AlreadyRemoved(); + revert Errors.AlreadyRemoved(); } _callHooksTarget(REMOVE_STRATEGY, _msgSender()); @@ -108,7 +108,7 @@ abstract contract AllocationPointsModule is Shared { // remove from withdrawalQueue IWithdrawalQueue($.withdrawalQueue).removeStrategyFromWithdrawalQueue(_strategy); - emit EventsLib.RemoveStrategy(_strategy); + emit Events.RemoveStrategy(_strategy); } } diff --git a/src/module/Fee.sol b/src/module/Fee.sol index 67404baf..1cec2773 100644 --- a/src/module/Fee.sol +++ b/src/module/Fee.sol @@ -10,8 +10,8 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Shared} from "../Shared.sol"; // libs import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; -import {ErrorsLib} from "../lib/ErrorsLib.sol"; -import {EventsLib} from "../lib/EventsLib.sol"; +import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; +import {EventsLib as Events} from "../lib/EventsLib.sol"; /// @title FeeModule contract /// @custom:security-contact security@euler.xyz @@ -26,9 +26,9 @@ abstract contract FeeModule is Shared { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); address feeRecipientCached = $.feeRecipient; - if (_newFeeRecipient == feeRecipientCached) revert ErrorsLib.FeeRecipientAlreadySet(); + if (_newFeeRecipient == feeRecipientCached) revert Errors.FeeRecipientAlreadySet(); - emit EventsLib.SetFeeRecipient(feeRecipientCached, _newFeeRecipient); + emit Events.SetFeeRecipient(feeRecipientCached, _newFeeRecipient); $.feeRecipient = _newFeeRecipient; } @@ -40,11 +40,11 @@ abstract contract FeeModule is Shared { uint256 performanceFeeCached = $.performanceFee; - if (_newFee > MAX_PERFORMANCE_FEE) revert ErrorsLib.MaxPerformanceFeeExceeded(); - if ($.feeRecipient == address(0)) revert ErrorsLib.FeeRecipientNotSet(); - if (_newFee == performanceFeeCached) revert ErrorsLib.PerformanceFeeAlreadySet(); + if (_newFee > MAX_PERFORMANCE_FEE) revert Errors.MaxPerformanceFeeExceeded(); + if ($.feeRecipient == address(0)) revert Errors.FeeRecipientNotSet(); + if (_newFee == performanceFeeCached) revert Errors.PerformanceFeeAlreadySet(); - emit EventsLib.SetPerformanceFee(performanceFeeCached, _newFee); + emit Events.SetPerformanceFee(performanceFeeCached, _newFee); $.performanceFee = _newFee; } diff --git a/src/module/Hooks.sol b/src/module/Hooks.sol index adf293fb..0c8c06a8 100644 --- a/src/module/Hooks.sol +++ b/src/module/Hooks.sol @@ -8,7 +8,7 @@ import {Shared} from "../Shared.sol"; // libs import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {HooksLib} from "../lib/HooksLib.sol"; -import {EventsLib} from "../lib/EventsLib.sol"; +import {EventsLib as Events} from "../lib/EventsLib.sol"; /// @title HooksModule contract /// @custom:security-contact security@euler.xyz @@ -22,7 +22,7 @@ abstract contract HooksModule is Shared { function setHooksConfig(address _hooksTarget, uint32 _hookedFns) external virtual nonReentrant { _setHooksConfig(_hooksTarget, _hookedFns); - emit EventsLib.SetHooksConfig(_hooksTarget, _hookedFns); + emit Events.SetHooksConfig(_hooksTarget, _hookedFns); } /// @notice Get the hooks contract and the hooked functions. diff --git a/src/module/Rewards.sol b/src/module/Rewards.sol index 722c0c54..ae80ee88 100644 --- a/src/module/Rewards.sol +++ b/src/module/Rewards.sol @@ -10,8 +10,8 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Shared} from "../Shared.sol"; // libs import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; -import {ErrorsLib} from "../lib/ErrorsLib.sol"; -import {EventsLib} from "../lib/EventsLib.sol"; +import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; +import {EventsLib as Events} from "../lib/EventsLib.sol"; /// @title Rewards module /// @custom:security-contact security@euler.xyz @@ -24,11 +24,11 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { function optInStrategyRewards(address _strategy) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - if (!$.strategies[_strategy].active) revert ErrorsLib.InactiveStrategy(); + if (!$.strategies[_strategy].active) revert Errors.InactiveStrategy(); IBalanceForwarder(_strategy).enableBalanceForwarder(); - emit EventsLib.OptInStrategyRewards(_strategy); + emit Events.OptInStrategyRewards(_strategy); } /// @notice Opt out of strategy rewards @@ -36,7 +36,7 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { function optOutStrategyRewards(address _strategy) external virtual nonReentrant { IBalanceForwarder(_strategy).disableBalanceForwarder(); - emit EventsLib.OptOutStrategyRewards(_strategy); + emit Events.OptOutStrategyRewards(_strategy); } /// @notice Enable aggregation layer vault rewards for specific strategy's reward token. @@ -45,11 +45,11 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { function enableRewardForStrategy(address _strategy, address _reward) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - if (!$.strategies[_strategy].active) revert ErrorsLib.InactiveStrategy(); + if (!$.strategies[_strategy].active) revert Errors.InactiveStrategy(); IRewardStreams(IBalanceForwarder(_strategy).balanceTrackerAddress()).enableReward(_strategy, _reward); - emit EventsLib.EnableRewardForStrategy(_strategy, _reward); + emit Events.EnableRewardForStrategy(_strategy, _reward); } /// @notice Disable aggregation layer vault rewards for specific strategy's reward token. @@ -63,13 +63,13 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - if (!$.strategies[_strategy].active) revert ErrorsLib.InactiveStrategy(); + if (!$.strategies[_strategy].active) revert Errors.InactiveStrategy(); IRewardStreams(IBalanceForwarder(_strategy).balanceTrackerAddress()).disableReward( _strategy, _reward, _forfeitRecentReward ); - emit EventsLib.DisableRewardForStrategy(_strategy, _reward, _forfeitRecentReward); + emit Events.DisableRewardForStrategy(_strategy, _reward, _forfeitRecentReward); } /// @notice Claim a specific strategy rewards @@ -123,13 +123,13 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); IBalanceTracker balanceTrackerCached = IBalanceTracker($.balanceTracker); - if (address(balanceTrackerCached) == address(0)) revert ErrorsLib.NotSupported(); - if ($.isBalanceForwarderEnabled[_sender]) revert ErrorsLib.AlreadyEnabled(); + if (address(balanceTrackerCached) == address(0)) revert Errors.NotSupported(); + if ($.isBalanceForwarderEnabled[_sender]) revert Errors.AlreadyEnabled(); $.isBalanceForwarderEnabled[_sender] = true; balanceTrackerCached.balanceTrackerHook(_sender, _senderBalance, false); - emit EventsLib.EnableBalanceForwarder(_sender); + emit Events.EnableBalanceForwarder(_sender); } /// @notice Disables balance forwarding for the authenticated account @@ -139,13 +139,13 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); IBalanceTracker balanceTrackerCached = IBalanceTracker($.balanceTracker); - if (address(balanceTrackerCached) == address(0)) revert ErrorsLib.NotSupported(); - if (!$.isBalanceForwarderEnabled[_sender]) revert ErrorsLib.AlreadyDisabled(); + if (address(balanceTrackerCached) == address(0)) revert Errors.NotSupported(); + if (!$.isBalanceForwarderEnabled[_sender]) revert Errors.AlreadyDisabled(); $.isBalanceForwarderEnabled[_sender] = false; balanceTrackerCached.balanceTrackerHook(_sender, 0, false); - emit EventsLib.DisableBalanceForwarder(_sender); + emit Events.DisableBalanceForwarder(_sender); } /// @notice Retrieves boolean indicating if the account opted in to forward balance changes to the rewards contract From 293dc42f61f885d722006c8aafade7661e481bd5 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:09:33 +0300 Subject: [PATCH 196/316] clean --- src/AggregationLayerVault.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/AggregationLayerVault.sol b/src/AggregationLayerVault.sol index 6277ad95..d2b0168a 100644 --- a/src/AggregationLayerVault.sol +++ b/src/AggregationLayerVault.sol @@ -489,7 +489,6 @@ contract AggregationLayerVault is _gulp(); } - /// TODO: better events /// @dev Execute harvest on a single strategy. /// @param _strategy Strategy address. /// @return yiled Amount of yield if any, else 0. From c454c6e8fef68159510b1c67b8cdf8f515488499 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 27 Jun 2024 13:24:56 +0300 Subject: [PATCH 197/316] rename --- ...yerVault.sol => EulerAggregationLayer.sol} | 23 +- ...y.sol => EulerAggregationLayerFactory.sol} | 24 +- ...erVault.sol => IEulerAggregationLayer.sol} | 6 +- src/lib/EventsLib.sol | 2 +- src/lib/StorageLib.sol | 6 +- src/module/AllocationPoints.sol | 8 +- src/plugin/Rebalancer.sol | 20 +- src/plugin/WithdrawalQueue.sol | 8 +- ....t.sol => EulerAggregationLayerBase.t.sol} | 74 ++-- test/e2e/BalanceForwarderE2ETest.t.sol | 136 +++--- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 386 +++++++++--------- test/e2e/HarvestRedeemE2ETest.t.sol | 104 ++--- test/e2e/HooksE2ETest.t.sol | 50 +-- test/e2e/PerformanceFeeE2ETest.t.sol | 96 ++--- test/e2e/StrategyCapE2ETest.t.sol | 96 ++--- test/e2e/StrategyRewardsE2ETest.t.sol | 20 +- .../fuzz/AdjustAllocationPointsFuzzTest.t.sol | 22 +- .../DepositWithdrawMintBurnFuzzTest.t.sol | 68 +-- test/unit/AddStrategyTest.t.sol | 12 +- test/unit/AdjustAllocationPointsTest.t.sol | 22 +- test/unit/GulpTest.t.sol | 118 +++--- test/unit/HarvestTest.t.sol | 176 ++++---- test/unit/RebalanceTest.t.sol | 220 +++++----- test/unit/RemoveStrategy.t.sol | 38 +- test/unit/ReorderWithdrawalQueueTest.t.sol | 16 +- 25 files changed, 876 insertions(+), 875 deletions(-) rename src/{AggregationLayerVault.sol => EulerAggregationLayer.sol} (97%) rename src/{AggregationLayerVaultFactory.sol => EulerAggregationLayerFactory.sol} (82%) rename src/interface/{IAggregationLayerVault.sol => IEulerAggregationLayer.sol} (94%) rename test/common/{AggregationLayerVaultBase.t.sol => EulerAggregationLayerBase.t.sol} (58%) diff --git a/src/AggregationLayerVault.sol b/src/EulerAggregationLayer.sol similarity index 97% rename from src/AggregationLayerVault.sol rename to src/EulerAggregationLayer.sol index d2b0168a..bdb46af3 100644 --- a/src/AggregationLayerVault.sol +++ b/src/EulerAggregationLayer.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; -import {IAggregationLayerVault} from "./interface/IAggregationLayerVault.sol"; +import {IEulerAggregationLayer} from "./interface/IEulerAggregationLayer.sol"; import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; // contracts import {Dispatch} from "./Dispatch.sol"; @@ -25,17 +25,17 @@ import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; import {ErrorsLib as Errors} from "./lib/ErrorsLib.sol"; import {EventsLib as Events} from "./lib/EventsLib.sol"; -/// @title AggregationLayerVault contract +/// @title EulerAggregationLayer contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens /// @dev inspired by Yearn v3 ❤️ -contract AggregationLayerVault is +contract EulerAggregationLayer is ERC4626Upgradeable, AccessControlEnumerableUpgradeable, Dispatch, - IAggregationLayerVault + IEulerAggregationLayer { using SafeERC20 for IERC20; using SafeCast for uint256; @@ -59,7 +59,7 @@ contract AggregationLayerVault is Dispatch(_rewardsModule, _hooksModule, _feeModule, _allocationPointsModule) {} - /// @notice Initialize the AggregationLayerVault. + /// @notice Initialize the EulerAggregationLayer. /// @param _initParams InitParams struct. function init(InitParams calldata _initParams) external initializer { __ERC4626_init_unchained(IERC20(_initParams.asset)); @@ -69,8 +69,8 @@ contract AggregationLayerVault is AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.locked = REENTRANCYLOCK__UNLOCKED; - $.withdrawalQueue = _initParams.withdrawalQueuePeriphery; - $.strategies[address(0)] = IAggregationLayerVault.Strategy({ + $.withdrawalQueue = _initParams.withdrawalQueuePlugin; + $.strategies[address(0)] = IEulerAggregationLayer.Strategy({ allocated: 0, allocationPoints: _initParams.initialCashAllocationPoints.toUint120(), active: true, @@ -82,8 +82,6 @@ contract AggregationLayerVault is // Setup DEFAULT_ADMIN _grantRole(DEFAULT_ADMIN_ROLE, _initParams.aggregationVaultOwner); - // By default, the Rebalancer contract is assigned the REBALANCER role - _grantRole(REBALANCER, _initParams.rebalancerPerihpery); // Setup role admins _setRoleAdmin(ALLOCATIONS_MANAGER, ALLOCATIONS_MANAGER_ADMIN); @@ -91,6 +89,9 @@ contract AggregationLayerVault is _setRoleAdmin(STRATEGY_REMOVER, STRATEGY_REMOVER_ADMIN); _setRoleAdmin(AGGREGATION_VAULT_MANAGER, AGGREGATION_VAULT_MANAGER_ADMIN); _setRoleAdmin(REBALANCER, REBALANCER_ADMIN); + + // By default, the Rebalancer contract is assigned the REBALANCER role + _grantRole(REBALANCER, _initParams.rebalancerPlugin); } /// @dev See {FeeModule-setFeeRecipient}. @@ -189,7 +190,7 @@ contract AggregationLayerVault is { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - IAggregationLayerVault.Strategy memory strategyData = $.strategies[_strategy]; + IEulerAggregationLayer.Strategy memory strategyData = $.strategies[_strategy]; if (_isDeposit) { // Do required approval (safely) and deposit @@ -269,7 +270,7 @@ contract AggregationLayerVault is /// @notice Get strategy params. /// @param _strategy strategy's address /// @return Strategy struct - function getStrategy(address _strategy) external view returns (IAggregationLayerVault.Strategy memory) { + function getStrategy(address _strategy) external view returns (IEulerAggregationLayer.Strategy memory) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.strategies[_strategy]; diff --git a/src/AggregationLayerVaultFactory.sol b/src/EulerAggregationLayerFactory.sol similarity index 82% rename from src/AggregationLayerVaultFactory.sol rename to src/EulerAggregationLayerFactory.sol index 4167ef58..fb38cb20 100644 --- a/src/AggregationLayerVaultFactory.sol +++ b/src/EulerAggregationLayerFactory.sol @@ -6,14 +6,14 @@ import {Rewards} from "./module/Rewards.sol"; import {Hooks} from "./module/Hooks.sol"; import {Fee} from "./module/Fee.sol"; import {WithdrawalQueue} from "./plugin/WithdrawalQueue.sol"; -import {AggregationLayerVault, IAggregationLayerVault} from "./AggregationLayerVault.sol"; +import {EulerAggregationLayer, IEulerAggregationLayer} from "./EulerAggregationLayer.sol"; // libs import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; -/// @title AggregationLayerVaultFactory contract +/// @title EulerAggregationLayerFactory contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) -contract AggregationLayerVaultFactory { +contract EulerAggregationLayerFactory { /// core dependencies address public immutable evc; address public immutable balanceTracker; @@ -62,7 +62,7 @@ contract AggregationLayerVaultFactory { /// @param _name Vaut name. /// @param _symbol Vault symbol. /// @param _initialCashAllocationPoints The amount of points to initally allocate for cash reserve. - /// @return aggregationLayerVault The address of the new deployed aggregation layer vault. + /// @return eulerAggregationLayer The address of the new deployed aggregation layer vault. function deployEulerAggregationLayer( address _asset, string memory _name, @@ -79,14 +79,14 @@ contract AggregationLayerVaultFactory { WithdrawalQueue withdrawalQueue = WithdrawalQueue(Clones.clone(withdrawalQueueImpl)); // deploy new aggregation vault - AggregationLayerVault aggregationLayerVault = - new AggregationLayerVault(rewardsModuleAddr, hooksModuleAddr, feeModuleAddr, allocationpointsModuleAddr); + EulerAggregationLayer eulerAggregationLayer = + new EulerAggregationLayer(rewardsModuleAddr, hooksModuleAddr, feeModuleAddr, allocationpointsModuleAddr); - IAggregationLayerVault.InitParams memory aggregationVaultInitParams = IAggregationLayerVault.InitParams({ + IEulerAggregationLayer.InitParams memory aggregationVaultInitParams = IEulerAggregationLayer.InitParams({ evc: evc, balanceTracker: balanceTracker, - withdrawalQueuePeriphery: address(withdrawalQueue), - rebalancerPerihpery: rebalancer, + withdrawalQueuePlugin: address(withdrawalQueue), + rebalancerPlugin: rebalancer, aggregationVaultOwner: msg.sender, asset: _asset, name: _name, @@ -94,9 +94,9 @@ contract AggregationLayerVaultFactory { initialCashAllocationPoints: _initialCashAllocationPoints }); - withdrawalQueue.init(msg.sender, address(aggregationLayerVault)); - aggregationLayerVault.init(aggregationVaultInitParams); + withdrawalQueue.init(msg.sender, address(eulerAggregationLayer)); + eulerAggregationLayer.init(aggregationVaultInitParams); - return address(aggregationLayerVault); + return address(eulerAggregationLayer); } } diff --git a/src/interface/IAggregationLayerVault.sol b/src/interface/IEulerAggregationLayer.sol similarity index 94% rename from src/interface/IAggregationLayerVault.sol rename to src/interface/IEulerAggregationLayer.sol index 9d37ae7d..cbabbb13 100644 --- a/src/interface/IAggregationLayerVault.sol +++ b/src/interface/IEulerAggregationLayer.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -interface IAggregationLayerVault { +interface IEulerAggregationLayer { /// @dev Struct to pass init() call params. struct InitParams { address evc; address balanceTracker; - address withdrawalQueuePeriphery; - address rebalancerPerihpery; + address withdrawalQueuePlugin; + address rebalancerPlugin; address aggregationVaultOwner; address asset; string name; diff --git a/src/lib/EventsLib.sol b/src/lib/EventsLib.sol index 199e9707..024b6915 100644 --- a/src/lib/EventsLib.sol +++ b/src/lib/EventsLib.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; library EventsLib { - /// @dev AggregationLayerVault events + /// @dev EulerAggregationLayer events event Gulp(uint256 interestLeft, uint256 interestSmearEnd); event AccruePerformanceFee(address indexed feeRecipient, uint256 yield, uint256 feeAssets); event Rebalance(address indexed strategy, uint256 amountToRebalance, bool isDeposit); diff --git a/src/lib/StorageLib.sol b/src/lib/StorageLib.sol index 8b4f3907..7f2b8073 100644 --- a/src/lib/StorageLib.sol +++ b/src/lib/StorageLib.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.0; // interfaces -import {IAggregationLayerVault} from "../interface/IAggregationLayerVault.sol"; +import {IEulerAggregationLayer} from "../interface/IEulerAggregationLayer.sol"; /// @custom:storage-location erc7201:euler_aggregation_vault.storage.AggregationVault struct AggregationVaultStorage { /// EVC address address evc; - /// Total amount of _asset deposited into AggregationLayerVault contract + /// Total amount of _asset deposited into EulerAggregationLayer contract uint256 totalAssetsDeposited; /// Total amount of _asset deposited across all strategies. uint256 totalAllocated; @@ -21,7 +21,7 @@ struct AggregationVaultStorage { /// Withdrawal queue contract's address address withdrawalQueue; /// Mapping between strategy address and it's allocation config - mapping(address => IAggregationLayerVault.Strategy) strategies; + mapping(address => IEulerAggregationLayer.Strategy) strategies; /// lastInterestUpdate: last timestamo where interest was updated. diff --git a/src/module/AllocationPoints.sol b/src/module/AllocationPoints.sol index c88baca4..b9106a77 100644 --- a/src/module/AllocationPoints.sol +++ b/src/module/AllocationPoints.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; // interfaces import {IERC4626} from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; import {IWithdrawalQueue} from "../interface/IWithdrawalQueue.sol"; -import {IAggregationLayerVault} from "../interface/IAggregationLayerVault.sol"; +import {IEulerAggregationLayer} from "../interface/IEulerAggregationLayer.sol"; // contracts import {Shared} from "../Shared.sol"; // libs @@ -26,7 +26,7 @@ abstract contract AllocationPointsModule is Shared { function adjustAllocationPoints(address _strategy, uint256 _newPoints) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - IAggregationLayerVault.Strategy memory strategyDataCache = $.strategies[_strategy]; + IEulerAggregationLayer.Strategy memory strategyDataCache = $.strategies[_strategy]; if (!strategyDataCache.active) { revert Errors.InactiveStrategy(); @@ -71,7 +71,7 @@ abstract contract AllocationPointsModule is Shared { _callHooksTarget(ADD_STRATEGY, _msgSender()); - $.strategies[_strategy] = IAggregationLayerVault.Strategy({ + $.strategies[_strategy] = IEulerAggregationLayer.Strategy({ allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, @@ -93,7 +93,7 @@ abstract contract AllocationPointsModule is Shared { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - IAggregationLayerVault.Strategy storage strategyStorage = $.strategies[_strategy]; + IEulerAggregationLayer.Strategy storage strategyStorage = $.strategies[_strategy]; if (!strategyStorage.active) { revert Errors.AlreadyRemoved(); diff --git a/src/plugin/Rebalancer.sol b/src/plugin/Rebalancer.sol index 302de564..14c663fc 100644 --- a/src/plugin/Rebalancer.sol +++ b/src/plugin/Rebalancer.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.0; // interfaces -import {IAggregationLayerVault} from "../interface/IAggregationLayerVault.sol"; +import {IEulerAggregationLayer} from "../interface/IEulerAggregationLayer.sol"; import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; /// @title Rebalancer plugin /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) -/// @notice A contract to execute rebalance() on the AggregationLayerVault. +/// @notice A contract to execute rebalance() on the EulerAggregationLayer. /// @dev Usually this contract will hold the `REBALANCER` role. contract Rebalancer { event ExecuteRebalance( @@ -23,7 +23,7 @@ contract Rebalancer { /// @param _curatedVault Curated vault address. /// @param _strategies Strategies addresses. function executeRebalance(address _curatedVault, address[] calldata _strategies) external { - IAggregationLayerVault(_curatedVault).gulp(); + IEulerAggregationLayer(_curatedVault).gulp(); for (uint256 i; i < _strategies.length; ++i) { _rebalance(_curatedVault, _strategies[i]); @@ -41,11 +41,11 @@ contract Rebalancer { return; //nothing to rebalance as that's the cash reserve } - IAggregationLayerVault.Strategy memory strategyData = - IAggregationLayerVault(_curatedVault).getStrategy(_strategy); + IEulerAggregationLayer.Strategy memory strategyData = + IEulerAggregationLayer(_curatedVault).getStrategy(_strategy); - uint256 totalAllocationPointsCache = IAggregationLayerVault(_curatedVault).totalAllocationPoints(); - uint256 totalAssetsAllocatableCache = IAggregationLayerVault(_curatedVault).totalAssetsAllocatable(); + uint256 totalAllocationPointsCache = IEulerAggregationLayer(_curatedVault).totalAllocationPoints(); + uint256 totalAssetsAllocatableCache = IEulerAggregationLayer(_curatedVault).totalAssetsAllocatable(); uint256 targetAllocation = totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; @@ -66,9 +66,9 @@ contract Rebalancer { } else if (strategyData.allocated < targetAllocation) { // Deposit uint256 targetCash = totalAssetsAllocatableCache - * IAggregationLayerVault(_curatedVault).getStrategy(address(0)).allocationPoints + * IEulerAggregationLayer(_curatedVault).getStrategy(address(0)).allocationPoints / totalAllocationPointsCache; - uint256 currentCash = totalAssetsAllocatableCache - IAggregationLayerVault(_curatedVault).totalAllocated(); + uint256 currentCash = totalAssetsAllocatableCache - IEulerAggregationLayer(_curatedVault).totalAllocated(); // Calculate available cash to put in strategies uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; @@ -90,7 +90,7 @@ contract Rebalancer { isDeposit = true; } - IAggregationLayerVault(_curatedVault).rebalance(_strategy, amountToRebalance, isDeposit); + IEulerAggregationLayer(_curatedVault).rebalance(_strategy, amountToRebalance, isDeposit); emit ExecuteRebalance(_curatedVault, _strategy, strategyData.allocated, targetAllocation, amountToRebalance); } diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index 118b63b4..5614403f 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; // interfaces import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; -import {IAggregationLayerVault} from "../interface/IAggregationLayerVault.sol"; +import {IEulerAggregationLayer} from "../interface/IEulerAggregationLayer.sol"; import {IWithdrawalQueue} from "../interface/IWithdrawalQueue.sol"; // contracts import {AccessControlEnumerableUpgradeable} from @@ -14,7 +14,7 @@ import {AccessControlEnumerableUpgradeable} from /// @author Euler Labs (https://www.eulerlabs.com/) /// @notice This contract manage the withdrawalQueue aray(add/remove strategy to the queue, re-order queue). /// Also it handles finishing the withdraw execution flow through the `callWithdrawalQueue()` function -/// that will be called by the AggregationLayerVault. +/// that will be called by the EulerAggregationLayer. contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue { error OutOfBounds(); error SameIndexes(); @@ -132,7 +132,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue uint256 desiredAssets = _assets - _availableAssets; uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; - IAggregationLayerVault(eulerAggregationVaultCached).executeStrategyWithdraw( + IEulerAggregationLayer(eulerAggregationVaultCached).executeStrategyWithdraw( address(strategy), withdrawAmount ); @@ -149,7 +149,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue revert NotEnoughAssets(); } - IAggregationLayerVault(eulerAggregationVaultCached).executeAggregationVaultWithdraw( + IEulerAggregationLayer(eulerAggregationVaultCached).executeAggregationVaultWithdraw( _caller, _receiver, _owner, _assets, _shares ); } diff --git a/test/common/AggregationLayerVaultBase.t.sol b/test/common/EulerAggregationLayerBase.t.sol similarity index 58% rename from test/common/AggregationLayerVaultBase.t.sol rename to test/common/EulerAggregationLayerBase.t.sol index 4c343a47..15412d58 100644 --- a/test/common/AggregationLayerVaultBase.t.sol +++ b/test/common/EulerAggregationLayerBase.t.sol @@ -6,18 +6,18 @@ import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; import {IWithdrawalQueue} from "../../src/interface/IWithdrawalQueue.sol"; // contracts import "evk/test/unit/evault/EVaultTestBase.t.sol"; -import {AggregationLayerVault, IAggregationLayerVault} from "../../src/AggregationLayerVault.sol"; +import {EulerAggregationLayer, IEulerAggregationLayer} from "../../src/EulerAggregationLayer.sol"; import {Rebalancer} from "../../src/plugin/Rebalancer.sol"; import {Hooks, HooksModule} from "../../src/module/Hooks.sol"; import {Rewards} from "../../src/module/Rewards.sol"; import {Fee} from "../../src/module/Fee.sol"; -import {AggregationLayerVaultFactory} from "../../src/AggregationLayerVaultFactory.sol"; +import {EulerAggregationLayerFactory} from "../../src/EulerAggregationLayerFactory.sol"; import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; import {AllocationPoints} from "../../src/module/AllocationPoints.sol"; // libs import {ErrorsLib} from "../../src/lib/ErrorsLib.sol"; -contract AggregationLayerVaultBase is EVaultTestBase { +contract EulerAggregationLayerBase is EVaultTestBase { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; address deployer; @@ -34,8 +34,8 @@ contract AggregationLayerVaultBase is EVaultTestBase { Rebalancer rebalancer; WithdrawalQueue withdrawalQueueImpl; - AggregationLayerVaultFactory aggregationLayerVaultFactory; - AggregationLayerVault aggregationLayerVault; + EulerAggregationLayerFactory eulerAggregationLayerFactory; + EulerAggregationLayer eulerAggregationLayer; WithdrawalQueue withdrawalQueue; function setUp() public virtual override { @@ -54,7 +54,7 @@ contract AggregationLayerVaultBase is EVaultTestBase { rebalancer = new Rebalancer(); withdrawalQueueImpl = new WithdrawalQueue(); - AggregationLayerVaultFactory.FactoryParams memory factoryParams = AggregationLayerVaultFactory.FactoryParams({ + EulerAggregationLayerFactory.FactoryParams memory factoryParams = EulerAggregationLayerFactory.FactoryParams({ evc: address(evc), balanceTracker: address(0), rewardsModuleImpl: address(rewardsImpl), @@ -64,77 +64,77 @@ contract AggregationLayerVaultBase is EVaultTestBase { rebalancer: address(rebalancer), withdrawalQueueImpl: address(withdrawalQueueImpl) }); - aggregationLayerVaultFactory = new AggregationLayerVaultFactory(factoryParams); + eulerAggregationLayerFactory = new EulerAggregationLayerFactory(factoryParams); - aggregationLayerVault = AggregationLayerVault( - aggregationLayerVaultFactory.deployEulerAggregationLayer( + eulerAggregationLayer = EulerAggregationLayer( + eulerAggregationLayerFactory.deployEulerAggregationLayer( address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS ) ); - withdrawalQueue = WithdrawalQueue(aggregationLayerVault.withdrawalQueue()); + withdrawalQueue = WithdrawalQueue(eulerAggregationLayer.withdrawalQueue()); // grant admin roles to deployer - aggregationLayerVault.grantRole(aggregationLayerVault.ALLOCATIONS_MANAGER_ADMIN(), deployer); - aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_ADDER_ADMIN(), deployer); - aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_REMOVER_ADMIN(), deployer); - aggregationLayerVault.grantRole(aggregationLayerVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); - aggregationLayerVault.grantRole(aggregationLayerVault.REBALANCER_ADMIN(), deployer); + eulerAggregationLayer.grantRole(eulerAggregationLayer.ALLOCATIONS_MANAGER_ADMIN(), deployer); + eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_ADDER_ADMIN(), deployer); + eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_REMOVER_ADMIN(), deployer); + eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); + eulerAggregationLayer.grantRole(eulerAggregationLayer.REBALANCER_ADMIN(), deployer); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager - aggregationLayerVault.grantRole(aggregationLayerVault.ALLOCATIONS_MANAGER(), manager); - aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_ADDER(), manager); - aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_REMOVER(), manager); - aggregationLayerVault.grantRole(aggregationLayerVault.AGGREGATION_VAULT_MANAGER(), manager); + eulerAggregationLayer.grantRole(eulerAggregationLayer.ALLOCATIONS_MANAGER(), manager); + eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_ADDER(), manager); + eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_REMOVER(), manager); + eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER(), manager); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); vm.stopPrank(); } function testInitialParams() public { - AggregationLayerVault.Strategy memory cashReserve = aggregationLayerVault.getStrategy(address(0)); + EulerAggregationLayer.Strategy memory cashReserve = eulerAggregationLayer.getStrategy(address(0)); assertEq(cashReserve.allocated, 0); assertEq(cashReserve.allocationPoints, CASH_RESERVE_ALLOCATION_POINTS); assertEq(cashReserve.active, true); assertEq( - aggregationLayerVault.getRoleAdmin(aggregationLayerVault.ALLOCATIONS_MANAGER()), - aggregationLayerVault.ALLOCATIONS_MANAGER_ADMIN() + eulerAggregationLayer.getRoleAdmin(eulerAggregationLayer.ALLOCATIONS_MANAGER()), + eulerAggregationLayer.ALLOCATIONS_MANAGER_ADMIN() ); assertEq( - aggregationLayerVault.getRoleAdmin(aggregationLayerVault.STRATEGY_ADDER()), - aggregationLayerVault.STRATEGY_ADDER_ADMIN() + eulerAggregationLayer.getRoleAdmin(eulerAggregationLayer.STRATEGY_ADDER()), + eulerAggregationLayer.STRATEGY_ADDER_ADMIN() ); assertEq( - aggregationLayerVault.getRoleAdmin(aggregationLayerVault.STRATEGY_REMOVER()), - aggregationLayerVault.STRATEGY_REMOVER_ADMIN() + eulerAggregationLayer.getRoleAdmin(eulerAggregationLayer.STRATEGY_REMOVER()), + eulerAggregationLayer.STRATEGY_REMOVER_ADMIN() ); assertEq( - aggregationLayerVault.getRoleAdmin(aggregationLayerVault.AGGREGATION_VAULT_MANAGER()), - aggregationLayerVault.AGGREGATION_VAULT_MANAGER_ADMIN() + eulerAggregationLayer.getRoleAdmin(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER()), + eulerAggregationLayer.AGGREGATION_VAULT_MANAGER_ADMIN() ); assertEq( withdrawalQueue.getRoleAdmin(withdrawalQueue.WITHDRAW_QUEUE_MANAGER()), withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN() ); - assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.ALLOCATIONS_MANAGER_ADMIN(), deployer)); - assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.STRATEGY_ADDER_ADMIN(), deployer)); - assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.STRATEGY_REMOVER_ADMIN(), deployer)); - assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer)); + assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.ALLOCATIONS_MANAGER_ADMIN(), deployer)); + assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.STRATEGY_ADDER_ADMIN(), deployer)); + assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.STRATEGY_REMOVER_ADMIN(), deployer)); + assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); - assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.ALLOCATIONS_MANAGER(), manager)); - assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.STRATEGY_ADDER(), manager)); - assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.STRATEGY_REMOVER(), manager)); - assertTrue(aggregationLayerVault.hasRole(aggregationLayerVault.AGGREGATION_VAULT_MANAGER(), manager)); + assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.ALLOCATIONS_MANAGER(), manager)); + assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.STRATEGY_ADDER(), manager)); + assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.STRATEGY_REMOVER(), manager)); + assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER(), manager)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); } function _addStrategy(address from, address strategy, uint256 allocationPoints) internal { vm.prank(from); - aggregationLayerVault.addStrategy(strategy, allocationPoints); + eulerAggregationLayer.addStrategy(strategy, allocationPoints); } function _getWithdrawalQueueLength() internal view returns (uint256) { diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 7ae48d56..25d07165 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -3,18 +3,18 @@ pragma solidity ^0.8.0; import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol"; import { - AggregationLayerVaultBase, - AggregationLayerVault, + EulerAggregationLayerBase, + EulerAggregationLayer, console2, EVault, IEVault, IRMTestDefault, TestERC20, - AggregationLayerVaultFactory, + EulerAggregationLayerFactory, Rewards -} from "../common/AggregationLayerVaultBase.t.sol"; +} from "../common/EulerAggregationLayerBase.t.sol"; -contract BalanceForwarderE2ETest is AggregationLayerVaultBase { +contract BalanceForwarderE2ETest is EulerAggregationLayerBase { uint256 user1InitialBalance = 100000e18; address trackingReward; @@ -25,7 +25,7 @@ contract BalanceForwarderE2ETest is AggregationLayerVaultBase { vm.startPrank(deployer); trackingReward = address(new TrackingRewardStreams(address(evc), 2 weeks)); - AggregationLayerVaultFactory.FactoryParams memory factoryParams = AggregationLayerVaultFactory.FactoryParams({ + EulerAggregationLayerFactory.FactoryParams memory factoryParams = EulerAggregationLayerFactory.FactoryParams({ evc: address(evc), balanceTracker: trackingReward, rewardsModuleImpl: address(rewardsImpl), @@ -35,26 +35,26 @@ contract BalanceForwarderE2ETest is AggregationLayerVaultBase { rebalancer: address(rebalancer), withdrawalQueueImpl: address(withdrawalQueueImpl) }); - aggregationLayerVaultFactory = new AggregationLayerVaultFactory(factoryParams); - aggregationLayerVault = AggregationLayerVault( - aggregationLayerVaultFactory.deployEulerAggregationLayer( + eulerAggregationLayerFactory = new EulerAggregationLayerFactory(factoryParams); + eulerAggregationLayer = EulerAggregationLayer( + eulerAggregationLayerFactory.deployEulerAggregationLayer( address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS ) ); // grant admin roles to deployer - aggregationLayerVault.grantRole(aggregationLayerVault.ALLOCATIONS_MANAGER_ADMIN(), deployer); - // aggregationLayerVault.grantRole(aggregationLayerVault.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); - aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_ADDER_ADMIN(), deployer); - aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_REMOVER_ADMIN(), deployer); - aggregationLayerVault.grantRole(aggregationLayerVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); + eulerAggregationLayer.grantRole(eulerAggregationLayer.ALLOCATIONS_MANAGER_ADMIN(), deployer); + // eulerAggregationLayer.grantRole(eulerAggregationLayer.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); + eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_ADDER_ADMIN(), deployer); + eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_REMOVER_ADMIN(), deployer); + eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); // grant roles to manager - aggregationLayerVault.grantRole(aggregationLayerVault.ALLOCATIONS_MANAGER(), manager); - // aggregationLayerVault.grantRole(aggregationLayerVault.WITHDRAW_QUEUE_MANAGER(), manager); - aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_ADDER(), manager); - aggregationLayerVault.grantRole(aggregationLayerVault.STRATEGY_REMOVER(), manager); - aggregationLayerVault.grantRole(aggregationLayerVault.AGGREGATION_VAULT_MANAGER(), manager); + eulerAggregationLayer.grantRole(eulerAggregationLayer.ALLOCATIONS_MANAGER(), manager); + // eulerAggregationLayer.grantRole(eulerAggregationLayer.WITHDRAW_QUEUE_MANAGER(), manager); + eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_ADDER(), manager); + eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_REMOVER(), manager); + eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; @@ -64,125 +64,125 @@ contract BalanceForwarderE2ETest is AggregationLayerVaultBase { // deposit into aggregator uint256 amountToDeposit = 10000e18; { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } } function testBalanceForwarderrAddress_Integrity() public view { - assertEq(aggregationLayerVault.balanceTrackerAddress(), trackingReward); + assertEq(eulerAggregationLayer.balanceTrackerAddress(), trackingReward); } function testEnableBalanceForwarder() public { vm.prank(user1); - aggregationLayerVault.enableBalanceForwarder(); + eulerAggregationLayer.enableBalanceForwarder(); - assertTrue(aggregationLayerVault.balanceForwarderEnabled(user1)); + assertTrue(eulerAggregationLayer.balanceForwarderEnabled(user1)); assertEq( - TrackingRewardStreams(trackingReward).balanceOf(user1, address(aggregationLayerVault)), - aggregationLayerVault.balanceOf(user1) + TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationLayer)), + eulerAggregationLayer.balanceOf(user1) ); } function testDisableBalanceForwarder() public { vm.prank(user1); - aggregationLayerVault.enableBalanceForwarder(); + eulerAggregationLayer.enableBalanceForwarder(); - assertTrue(aggregationLayerVault.balanceForwarderEnabled(user1)); + assertTrue(eulerAggregationLayer.balanceForwarderEnabled(user1)); vm.prank(user1); - aggregationLayerVault.disableBalanceForwarder(); + eulerAggregationLayer.disableBalanceForwarder(); - assertFalse(aggregationLayerVault.balanceForwarderEnabled(user1)); - assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(aggregationLayerVault)), 0); + assertFalse(eulerAggregationLayer.balanceForwarderEnabled(user1)); + assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationLayer)), 0); } function testHookWhenReceiverEnabled() public { vm.prank(user1); - aggregationLayerVault.enableBalanceForwarder(); + eulerAggregationLayer.enableBalanceForwarder(); // deposit into aggregator uint256 amountToDeposit = 10000e18; { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); assertEq( - TrackingRewardStreams(trackingReward).balanceOf(user1, address(aggregationLayerVault)), - aggregationLayerVault.balanceOf(user1) + TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationLayer)), + eulerAggregationLayer.balanceOf(user1) ); } } function testHookWhenSenderEnabled() public { vm.prank(user1); - aggregationLayerVault.enableBalanceForwarder(); + eulerAggregationLayer.enableBalanceForwarder(); // deposit into aggregator uint256 amountToDeposit = 10000e18; { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); assertEq( - TrackingRewardStreams(trackingReward).balanceOf(user1, address(aggregationLayerVault)), - aggregationLayerVault.balanceOf(user1) + TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationLayer)), + eulerAggregationLayer.balanceOf(user1) ); } { - uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - aggregationLayerVault.redeem(amountToWithdraw, user1, user1); + eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(aggregationLayerVault)), 0); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(eulerAggregationLayer)), 0); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq( assetTST.balanceOf(user1), - user1AssetTSTBalanceBefore + aggregationLayerVault.convertToAssets(amountToWithdraw) + user1AssetTSTBalanceBefore + eulerAggregationLayer.convertToAssets(amountToWithdraw) ); - assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(aggregationLayerVault)), 0); + assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationLayer)), 0); } } } diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 5c4ae3d8..0c6f7f8b 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.0; import { - AggregationLayerVaultBase, - AggregationLayerVault, + EulerAggregationLayerBase, + EulerAggregationLayer, console2, EVault, IEVault, IRMTestDefault, TestERC20, WithdrawalQueue, - IAggregationLayerVault -} from "../common/AggregationLayerVaultBase.t.sol"; + IEulerAggregationLayer +} from "../common/EulerAggregationLayerBase.t.sol"; -contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { +contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -30,41 +30,41 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); + assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); assertEq( - (aggregationLayerVault.getStrategy(address(eTST))).allocated, + (eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash ); } @@ -73,37 +73,37 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // partial withdraw, no need to withdraw from strategy as cash reserve is enough uint256 amountToWithdraw = 6000e18; { - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - uint256 strategyShareBalanceBefore = eTST.balanceOf(address(aggregationLayerVault)); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + uint256 strategyShareBalanceBefore = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - aggregationLayerVault.withdraw(amountToWithdraw, user1, user1); + eulerAggregationLayer.withdraw(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(aggregationLayerVault)), strategyShareBalanceBefore); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(eulerAggregationLayer)), strategyShareBalanceBefore); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated); } // full withdraw, will have to withdraw from strategy as cash reserve is not enough { amountToWithdraw = amountToDeposit - amountToWithdraw; - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - aggregationLayerVault.withdraw(amountToWithdraw, user1, user1); + eulerAggregationLayer.withdraw(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(aggregationLayerVault)), 0); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(eulerAggregationLayer)), 0); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, 0); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, 0); } } @@ -112,67 +112,67 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedStrategyCash); } vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(aggregationLayerVault)); + eTST.skim(type(uint256).max, address(eulerAggregationLayer)); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - aggregationLayerVault.redeem(amountToWithdraw, user1, user1); + eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(aggregationLayerVault)), yield); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(eulerAggregationLayer)), yield); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq( assetTST.balanceOf(user1), - user1AssetTSTBalanceBefore + aggregationLayerVault.convertToAssets(amountToWithdraw) + user1AssetTSTBalanceBefore + eulerAggregationLayer.convertToAssets(amountToWithdraw) ); } } @@ -197,19 +197,19 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -218,20 +218,20 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - IAggregationLayerVault.Strategy memory eTSTstrategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - IAggregationLayerVault.Strategy memory eTSTsecondarystrategyBefore = - aggregationLayerVault.getStrategy(address(eTSTsecondary)); + IEulerAggregationLayer.Strategy memory eTSTstrategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory eTSTsecondarystrategyBefore = + eulerAggregationLayer.getStrategy(address(eTSTsecondary)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), eTSTstrategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), eTSTstrategyBefore.allocated); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), eTSTsecondarystrategyBefore.allocated ); - uint256 expectedeTSTStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * eTSTstrategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); - uint256 expectedeTSTsecondaryStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * eTSTsecondarystrategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedeTSTStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); assertTrue(expectedeTSTStrategyCash != 0); assertTrue(expectedeTSTsecondaryStrategyCash != 0); @@ -240,31 +240,31 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); assertEq( - aggregationLayerVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash + eulerAggregationLayer.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash ); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedeTSTStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedeTSTStrategyCash); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), expectedeTSTsecondaryStrategyCash ); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); assertEq( - (aggregationLayerVault.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + (eulerAggregationLayer.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash ); assertEq( - assetTST.balanceOf(address(aggregationLayerVault)), + assetTST.balanceOf(address(eulerAggregationLayer)), amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) ); } vm.warp(block.timestamp + 86400); // mock an increase of aggregator balance due to yield - uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); - uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance); uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; uint256 aggrNeweTSTsecondaryUnderlyingBalance = aggrCurrenteTSTsecondaryUnderlyingBalance * 11e17 / 1e18; @@ -273,25 +273,25 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { assetTST.mint(address(eTST), eTSTYield); assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield); - eTST.skim(type(uint256).max, address(aggregationLayerVault)); - eTSTsecondary.skim(type(uint256).max, address(aggregationLayerVault)); + eTST.skim(type(uint256).max, address(eulerAggregationLayer)); + eTSTsecondary.skim(type(uint256).max, address(eulerAggregationLayer)); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - aggregationLayerVault.redeem(amountToWithdraw, user1, user1); + eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(aggregationLayerVault)), 0); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(eulerAggregationLayer)), 0); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq( assetTST.balanceOf(user1), - user1AssetTSTBalanceBefore + aggregationLayerVault.convertToAssets(amountToWithdraw) + user1AssetTSTBalanceBefore + eulerAggregationLayer.convertToAssets(amountToWithdraw) ); } } @@ -301,72 +301,72 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedStrategyCash); } vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(aggregationLayerVault)); + eTST.skim(type(uint256).max, address(eulerAggregationLayer)); // harvest vm.prank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); vm.warp(block.timestamp + 2 weeks); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - aggregationLayerVault.redeem(amountToWithdraw, user1, user1); + eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); // all yield is distributed - assertApproxEqAbs(eTST.balanceOf(address(aggregationLayerVault)), 0, 1); + assertApproxEqAbs(eTST.balanceOf(address(eulerAggregationLayer)), 0, 1); assertApproxEqAbs( - aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1 + eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1 ); - assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToDeposit + yield, 1); } } @@ -391,19 +391,19 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -412,20 +412,20 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - IAggregationLayerVault.Strategy memory eTSTstrategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - IAggregationLayerVault.Strategy memory eTSTsecondarystrategyBefore = - aggregationLayerVault.getStrategy(address(eTSTsecondary)); + IEulerAggregationLayer.Strategy memory eTSTstrategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory eTSTsecondarystrategyBefore = + eulerAggregationLayer.getStrategy(address(eTSTsecondary)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), eTSTstrategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), eTSTstrategyBefore.allocated); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), eTSTsecondarystrategyBefore.allocated ); - uint256 expectedeTSTStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * eTSTstrategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); - uint256 expectedeTSTsecondaryStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * eTSTsecondarystrategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedeTSTStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); assertTrue(expectedeTSTStrategyCash != 0); assertTrue(expectedeTSTsecondaryStrategyCash != 0); @@ -434,22 +434,22 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); assertEq( - aggregationLayerVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash + eulerAggregationLayer.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash ); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedeTSTStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedeTSTStrategyCash); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), expectedeTSTsecondaryStrategyCash ); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); assertEq( - (aggregationLayerVault.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + (eulerAggregationLayer.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash ); assertEq( - assetTST.balanceOf(address(aggregationLayerVault)), + assetTST.balanceOf(address(eulerAggregationLayer)), amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) ); } @@ -459,9 +459,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { uint256 eTSTsecondaryYield; { // mock an increase of aggregator balance due to yield - uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); - uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance); uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; @@ -471,30 +471,30 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { assetTST.mint(address(eTST), eTSTYield); assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield); - eTST.skim(type(uint256).max, address(aggregationLayerVault)); - eTSTsecondary.skim(type(uint256).max, address(aggregationLayerVault)); + eTST.skim(type(uint256).max, address(eulerAggregationLayer)); + eTSTsecondary.skim(type(uint256).max, address(eulerAggregationLayer)); } // harvest vm.prank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); vm.warp(block.timestamp + 2 weeks); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - aggregationLayerVault.redeem(amountToWithdraw, user1, user1); + eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); - assertApproxEqAbs(eTST.balanceOf(address(aggregationLayerVault)), 0, 0); + assertApproxEqAbs(eTST.balanceOf(address(eulerAggregationLayer)), 0, 0); assertApproxEqAbs( - aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1 + eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1 ); - assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertApproxEqAbs( assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToDeposit + eTSTYield + eTSTsecondaryYield, @@ -523,19 +523,19 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -544,20 +544,20 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - IAggregationLayerVault.Strategy memory eTSTstrategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - IAggregationLayerVault.Strategy memory eTSTsecondarystrategyBefore = - aggregationLayerVault.getStrategy(address(eTSTsecondary)); + IEulerAggregationLayer.Strategy memory eTSTstrategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory eTSTsecondarystrategyBefore = + eulerAggregationLayer.getStrategy(address(eTSTsecondary)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), eTSTstrategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), eTSTstrategyBefore.allocated); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), eTSTsecondarystrategyBefore.allocated ); - uint256 expectedeTSTStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * eTSTstrategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); - uint256 expectedeTSTsecondaryStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * eTSTsecondarystrategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedeTSTStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); assertTrue(expectedeTSTStrategyCash != 0); assertTrue(expectedeTSTsecondaryStrategyCash != 0); @@ -566,22 +566,22 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); assertEq( - aggregationLayerVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash + eulerAggregationLayer.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash ); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedeTSTStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedeTSTStrategyCash); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), expectedeTSTsecondaryStrategyCash ); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); assertEq( - (aggregationLayerVault.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + (eulerAggregationLayer.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash ); assertEq( - assetTST.balanceOf(address(aggregationLayerVault)), + assetTST.balanceOf(address(eulerAggregationLayer)), amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) ); } @@ -591,9 +591,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { uint256 eTSTsecondaryYield; { // mock an increase of aggregator balance due to yield - uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); - uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance); uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; @@ -603,24 +603,24 @@ contract DepositRebalanceHarvestWithdrawE2ETest is AggregationLayerVaultBase { assetTST.mint(address(eTST), eTSTYield); assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield); - eTST.skim(type(uint256).max, address(aggregationLayerVault)); - eTSTsecondary.skim(type(uint256).max, address(aggregationLayerVault)); + eTST.skim(type(uint256).max, address(eulerAggregationLayer)); + eTSTsecondary.skim(type(uint256).max, address(eulerAggregationLayer)); } // harvest vm.prank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); vm.warp(block.timestamp + 2 weeks); vm.prank(manager); - aggregationLayerVault.removeStrategy(address(eTSTsecondary)); + eulerAggregationLayer.removeStrategy(address(eTSTsecondary)); { - uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); + uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); vm.prank(user1); vm.expectRevert(WithdrawalQueue.NotEnoughAssets.selector); - aggregationLayerVault.redeem(amountToWithdraw, user1, user1); + eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); } } } diff --git a/test/e2e/HarvestRedeemE2ETest.t.sol b/test/e2e/HarvestRedeemE2ETest.t.sol index 1a3a43c4..54634721 100644 --- a/test/e2e/HarvestRedeemE2ETest.t.sol +++ b/test/e2e/HarvestRedeemE2ETest.t.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.0; import { - AggregationLayerVaultBase, - AggregationLayerVault, + EulerAggregationLayerBase, + EulerAggregationLayer, console2, EVault, - IAggregationLayerVault -} from "../common/AggregationLayerVaultBase.t.sol"; + IEulerAggregationLayer +} from "../common/EulerAggregationLayerBase.t.sol"; -contract HarvestRedeemE2ETest is AggregationLayerVaultBase { +contract HarvestRedeemE2ETest is EulerAggregationLayerBase { uint256 user1InitialBalance = 100000e18; uint256 amountToDeposit = 10000e18; @@ -23,40 +23,40 @@ contract HarvestRedeemE2ETest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedStrategyCash); } } @@ -64,36 +64,36 @@ contract HarvestRedeemE2ETest is AggregationLayerVaultBase { vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); assertTrue(expectedAllocated < strategyBefore.allocated); - uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(aggregationLayerVault)); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(eulerAggregationLayer)); - uint256 user1SharesBefore = aggregationLayerVault.balanceOf(user1); - uint256 user1SocializedLoss = user1SharesBefore * negativeYield / aggregationLayerVault.totalSupply(); + uint256 user1SharesBefore = eulerAggregationLayer.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / eulerAggregationLayer.totalSupply(); uint256 expectedUser1Assets = - user1SharesBefore * amountToDeposit / aggregationLayerVault.totalSupply() - user1SocializedLoss; + user1SharesBefore * amountToDeposit / eulerAggregationLayer.totalSupply() - user1SocializedLoss; uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - aggregationLayerVault.harvest(); - aggregationLayerVault.redeem(user1SharesBefore, user1, user1); + eulerAggregationLayer.harvest(); + eulerAggregationLayer.redeem(user1SharesBefore, user1, user1); vm.stopPrank(); - uint256 user1SharesAfter = aggregationLayerVault.balanceOf(user1); + uint256 user1SharesAfter = eulerAggregationLayer.balanceOf(user1); assertEq(user1SharesAfter, 0); assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedUser1Assets, 1); - assertEq(aggregationLayerVault.totalAssetsDeposited(), 0); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), 0); } function testHarvestNegativeYieldAndRedeemMultipleUser() public { @@ -102,55 +102,55 @@ contract HarvestRedeemE2ETest is AggregationLayerVaultBase { // deposit into aggregator { vm.startPrank(user2); - assetTST.approve(address(aggregationLayerVault), user2InitialBalance); - aggregationLayerVault.deposit(user2InitialBalance, user2); + assetTST.approve(address(eulerAggregationLayer), user2InitialBalance); + eulerAggregationLayer.deposit(user2InitialBalance, user2); vm.stopPrank(); } vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); assertTrue(expectedAllocated < strategyBefore.allocated); - uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(aggregationLayerVault)); - uint256 user1SharesBefore = aggregationLayerVault.balanceOf(user1); - uint256 user1SocializedLoss = user1SharesBefore * negativeYield / aggregationLayerVault.totalSupply(); - uint256 user2SharesBefore = aggregationLayerVault.balanceOf(user2); - uint256 user2SocializedLoss = user2SharesBefore * negativeYield / aggregationLayerVault.totalSupply(); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(eulerAggregationLayer)); + uint256 user1SharesBefore = eulerAggregationLayer.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / eulerAggregationLayer.totalSupply(); + uint256 user2SharesBefore = eulerAggregationLayer.balanceOf(user2); + uint256 user2SocializedLoss = user2SharesBefore * negativeYield / eulerAggregationLayer.totalSupply(); uint256 expectedUser1Assets = user1SharesBefore * (amountToDeposit + user2InitialBalance) - / aggregationLayerVault.totalSupply() - user1SocializedLoss; + / eulerAggregationLayer.totalSupply() - user1SocializedLoss; uint256 expectedUser2Assets = user2SharesBefore * (amountToDeposit + user2InitialBalance) - / aggregationLayerVault.totalSupply() - user2SocializedLoss; + / eulerAggregationLayer.totalSupply() - user2SocializedLoss; uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); uint256 user2AssetTSTBalanceBefore = assetTST.balanceOf(user2); vm.startPrank(user1); - aggregationLayerVault.harvest(); - aggregationLayerVault.redeem(user1SharesBefore, user1, user1); + eulerAggregationLayer.harvest(); + eulerAggregationLayer.redeem(user1SharesBefore, user1, user1); vm.stopPrank(); vm.prank(user2); - aggregationLayerVault.redeem(user2SharesBefore, user2, user2); + eulerAggregationLayer.redeem(user2SharesBefore, user2, user2); - uint256 user1SharesAfter = aggregationLayerVault.balanceOf(user1); - uint256 user2SharesAfter = aggregationLayerVault.balanceOf(user2); + uint256 user1SharesAfter = eulerAggregationLayer.balanceOf(user1); + uint256 user2SharesAfter = eulerAggregationLayer.balanceOf(user2); assertEq(user1SharesAfter, 0); assertEq(user2SharesAfter, 0); assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedUser1Assets, 1); assertApproxEqAbs(assetTST.balanceOf(user2), user2AssetTSTBalanceBefore + expectedUser2Assets, 1); - assertEq(aggregationLayerVault.totalAssetsDeposited(), 0); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), 0); } } diff --git a/test/e2e/HooksE2ETest.t.sol b/test/e2e/HooksE2ETest.t.sol index 0d33f8fd..04a87aee 100644 --- a/test/e2e/HooksE2ETest.t.sol +++ b/test/e2e/HooksE2ETest.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import { - AggregationLayerVaultBase, - AggregationLayerVault, + EulerAggregationLayerBase, + EulerAggregationLayer, console2, EVault, IEVault, @@ -11,9 +11,9 @@ import { TestERC20, IHookTarget, ErrorsLib -} from "../common/AggregationLayerVaultBase.t.sol"; +} from "../common/EulerAggregationLayerBase.t.sol"; -contract HooksE2ETest is AggregationLayerVaultBase { +contract HooksE2ETest is EulerAggregationLayerBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -26,38 +26,38 @@ contract HooksE2ETest is AggregationLayerVaultBase { } function testSetHooksConfig() public { - uint32 expectedHookedFns = aggregationLayerVault.DEPOSIT() | aggregationLayerVault.WITHDRAW() - | aggregationLayerVault.ADD_STRATEGY() | aggregationLayerVault.REMOVE_STRATEGY(); + uint32 expectedHookedFns = eulerAggregationLayer.DEPOSIT() | eulerAggregationLayer.WITHDRAW() + | eulerAggregationLayer.ADD_STRATEGY() | eulerAggregationLayer.REMOVE_STRATEGY(); vm.startPrank(manager); address hooksContract = address(new HooksContract()); - aggregationLayerVault.setHooksConfig(hooksContract, expectedHookedFns); + eulerAggregationLayer.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); - (address hookTarget, uint32 hookedFns) = aggregationLayerVault.getHooksConfig(); + (address hookTarget, uint32 hookedFns) = eulerAggregationLayer.getHooksConfig(); assertEq(hookTarget, hooksContract); assertEq(hookedFns, expectedHookedFns); } function testSetHooksConfigWithAddressZero() public { - uint32 expectedHookedFns = aggregationLayerVault.DEPOSIT() | aggregationLayerVault.WITHDRAW() - | aggregationLayerVault.ADD_STRATEGY() | aggregationLayerVault.REMOVE_STRATEGY(); + uint32 expectedHookedFns = eulerAggregationLayer.DEPOSIT() | eulerAggregationLayer.WITHDRAW() + | eulerAggregationLayer.ADD_STRATEGY() | eulerAggregationLayer.REMOVE_STRATEGY(); vm.startPrank(manager); vm.expectRevert(ErrorsLib.InvalidHooksTarget.selector); - aggregationLayerVault.setHooksConfig(address(0), expectedHookedFns); + eulerAggregationLayer.setHooksConfig(address(0), expectedHookedFns); vm.stopPrank(); } function testSetHooksConfigWithNotHooksContract() public { - uint32 expectedHookedFns = aggregationLayerVault.DEPOSIT() | aggregationLayerVault.WITHDRAW() - | aggregationLayerVault.ADD_STRATEGY() | aggregationLayerVault.REMOVE_STRATEGY(); + uint32 expectedHookedFns = eulerAggregationLayer.DEPOSIT() | eulerAggregationLayer.WITHDRAW() + | eulerAggregationLayer.ADD_STRATEGY() | eulerAggregationLayer.REMOVE_STRATEGY(); vm.startPrank(manager); address hooksContract = address(new NotHooksContract()); vm.expectRevert(ErrorsLib.NotHooksContract.selector); - aggregationLayerVault.setHooksConfig(hooksContract, expectedHookedFns); + eulerAggregationLayer.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); } @@ -66,33 +66,33 @@ contract HooksE2ETest is AggregationLayerVaultBase { vm.startPrank(manager); address hooksContract = address(new HooksContract()); vm.expectRevert(ErrorsLib.InvalidHookedFns.selector); - aggregationLayerVault.setHooksConfig(hooksContract, expectedHookedFns); + eulerAggregationLayer.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); } function testHookedDeposit() public { - uint32 expectedHookedFns = aggregationLayerVault.DEPOSIT(); + uint32 expectedHookedFns = eulerAggregationLayer.DEPOSIT(); vm.startPrank(manager); address hooksContract = address(new HooksContract()); - aggregationLayerVault.setHooksConfig(hooksContract, expectedHookedFns); + eulerAggregationLayer.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); uint256 amountToDeposit = 10000e18; // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } } diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 46593036..218916d4 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -2,17 +2,17 @@ pragma solidity ^0.8.0; import { - AggregationLayerVaultBase, - AggregationLayerVault, + EulerAggregationLayerBase, + EulerAggregationLayer, console2, EVault, IEVault, IRMTestDefault, TestERC20, - IAggregationLayerVault -} from "../common/AggregationLayerVaultBase.t.sol"; + IEulerAggregationLayer +} from "../common/EulerAggregationLayerBase.t.sol"; -contract PerformanceFeeE2ETest is AggregationLayerVaultBase { +contract PerformanceFeeE2ETest is EulerAggregationLayerBase { uint256 user1InitialBalance = 100000e18; address feeRecipient; @@ -30,18 +30,18 @@ contract PerformanceFeeE2ETest is AggregationLayerVaultBase { function testSetPerformanceFee() public { { - (, uint256 fee) = aggregationLayerVault.performanceFeeConfig(); + (, uint256 fee) = eulerAggregationLayer.performanceFeeConfig(); assertEq(fee, 0); } uint256 newPerformanceFee = 3e17; vm.startPrank(manager); - aggregationLayerVault.setFeeRecipient(feeRecipient); - aggregationLayerVault.setPerformanceFee(newPerformanceFee); + eulerAggregationLayer.setFeeRecipient(feeRecipient); + eulerAggregationLayer.setPerformanceFee(newPerformanceFee); vm.stopPrank(); - (address feeRecipientAddr, uint256 fee) = aggregationLayerVault.performanceFeeConfig(); + (address feeRecipientAddr, uint256 fee) = eulerAggregationLayer.performanceFeeConfig(); assertEq(fee, newPerformanceFee); assertEq(feeRecipientAddr, feeRecipient); } @@ -50,111 +50,111 @@ contract PerformanceFeeE2ETest is AggregationLayerVaultBase { uint256 newPerformanceFee = 3e17; vm.startPrank(manager); - aggregationLayerVault.setFeeRecipient(feeRecipient); - aggregationLayerVault.setPerformanceFee(newPerformanceFee); + eulerAggregationLayer.setFeeRecipient(feeRecipient); + eulerAggregationLayer.setPerformanceFee(newPerformanceFee); vm.stopPrank(); uint256 amountToDeposit = 10000e18; // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedStrategyCash); } vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% uint256 yield; { - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(aggregationLayerVault)); + eTST.skim(type(uint256).max, address(eulerAggregationLayer)); } - (, uint256 performanceFee) = aggregationLayerVault.performanceFeeConfig(); + (, uint256 performanceFee) = eulerAggregationLayer.performanceFeeConfig(); uint256 expectedPerformanceFee = yield * performanceFee / 1e18; - IAggregationLayerVault.Strategy memory strategyBeforeHarvest = aggregationLayerVault.getStrategy(address(eTST)); - uint256 totalAllocatedBefore = aggregationLayerVault.totalAllocated(); + IEulerAggregationLayer.Strategy memory strategyBeforeHarvest = eulerAggregationLayer.getStrategy(address(eTST)); + uint256 totalAllocatedBefore = eulerAggregationLayer.totalAllocated(); // harvest vm.prank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); assertEq(assetTST.balanceOf(feeRecipient), expectedPerformanceFee); assertEq( - aggregationLayerVault.getStrategy(address(eTST)).allocated, + eulerAggregationLayer.getStrategy(address(eTST)).allocated, strategyBeforeHarvest.allocated + yield - expectedPerformanceFee ); - assertEq(aggregationLayerVault.totalAllocated(), totalAllocatedBefore + yield - expectedPerformanceFee); + assertEq(eulerAggregationLayer.totalAllocated(), totalAllocatedBefore + yield - expectedPerformanceFee); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = aggregationLayerVault.balanceOf(user1); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = aggregationLayerVault.totalSupply(); + uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); - uint256 expectedAssetTST = aggregationLayerVault.convertToAssets(aggregationLayerVault.balanceOf(user1)); + uint256 expectedAssetTST = eulerAggregationLayer.convertToAssets(eulerAggregationLayer.balanceOf(user1)); vm.prank(user1); - aggregationLayerVault.redeem(amountToWithdraw, user1, user1); + eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); assertApproxEqAbs( - aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssetTST, 1 + eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssetTST, 1 ); - assertEq(aggregationLayerVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedAssetTST, 1); } // full withdraw of recipient fees { - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 assetTSTBalanceBefore = assetTST.balanceOf(feeRecipient); - uint256 feeShares = aggregationLayerVault.balanceOf(feeRecipient); - uint256 expectedAssets = aggregationLayerVault.convertToAssets(feeShares); + uint256 feeShares = eulerAggregationLayer.balanceOf(feeRecipient); + uint256 expectedAssets = eulerAggregationLayer.convertToAssets(feeShares); vm.prank(feeRecipient); - aggregationLayerVault.redeem(feeShares, feeRecipient, feeRecipient); + eulerAggregationLayer.redeem(feeShares, feeRecipient, feeRecipient); assertApproxEqAbs( - aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssets, 1 + eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssets, 1 ); - assertEq(aggregationLayerVault.totalSupply(), 0); + assertEq(eulerAggregationLayer.totalSupply(), 0); assertApproxEqAbs(assetTST.balanceOf(feeRecipient), assetTSTBalanceBefore + expectedAssets, 1); } } diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index 88bf5b6b..a94638e6 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.0; import { - AggregationLayerVaultBase, - AggregationLayerVault, + EulerAggregationLayerBase, + EulerAggregationLayer, console2, EVault, IEVault, IRMTestDefault, TestERC20, - IAggregationLayerVault, + IEulerAggregationLayer, ErrorsLib -} from "../common/AggregationLayerVaultBase.t.sol"; +} from "../common/EulerAggregationLayerBase.t.sol"; -contract StrategyCapE2ETest is AggregationLayerVaultBase { +contract StrategyCapE2ETest is EulerAggregationLayerBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -28,12 +28,12 @@ contract StrategyCapE2ETest is AggregationLayerVaultBase { function testSetCap() public { uint256 cap = 1000000e18; - assertEq((aggregationLayerVault.getStrategy(address(eTST))).cap, 0); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).cap, 0); vm.prank(manager); - aggregationLayerVault.setStrategyCap(address(eTST), cap); + eulerAggregationLayer.setStrategyCap(address(eTST), cap); - IAggregationLayerVault.Strategy memory strategy = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategy = eulerAggregationLayer.getStrategy(address(eTST)); assertEq(strategy.cap, cap); } @@ -43,53 +43,53 @@ contract StrategyCapE2ETest is AggregationLayerVaultBase { vm.prank(manager); vm.expectRevert(ErrorsLib.InactiveStrategy.selector); - aggregationLayerVault.setStrategyCap(address(0x2), cap); + eulerAggregationLayer.setStrategyCap(address(0x2), cap); } function testRebalanceAfterHittingCap() public { uint256 cap = 3333333333333333333333; vm.prank(manager); - aggregationLayerVault.setStrategyCap(address(eTST), cap); + eulerAggregationLayer.setStrategyCap(address(eTST), cap); uint256 amountToDeposit = 10000e18; // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); + assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); assertEq( - (aggregationLayerVault.getStrategy(address(eTST))).allocated, + (eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash ); } @@ -98,17 +98,17 @@ contract StrategyCapE2ETest is AggregationLayerVaultBase { vm.warp(block.timestamp + 86400); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); - uint256 strategyAllocatedBefore = (aggregationLayerVault.getStrategy(address(eTST))).allocated; + uint256 strategyAllocatedBefore = (eulerAggregationLayer.getStrategy(address(eTST))).allocated; address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); vm.stopPrank(); - assertEq(strategyAllocatedBefore, (aggregationLayerVault.getStrategy(address(eTST))).allocated); + assertEq(strategyAllocatedBefore, (eulerAggregationLayer.getStrategy(address(eTST))).allocated); } function testRebalanceWhentargetAllocationGreaterThanCap() public { @@ -116,45 +116,45 @@ contract StrategyCapE2ETest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); // set cap 10% less than target allocation uint256 cap = expectedStrategyCash * 9e17 / 1e18; vm.prank(manager); - aggregationLayerVault.setStrategyCap(address(eTST), cap); + eulerAggregationLayer.setStrategyCap(address(eTST), cap); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - assertEq(aggregationLayerVault.totalAllocated(), cap); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), cap); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated + cap); + assertEq(eulerAggregationLayer.totalAllocated(), cap); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), cap); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated + cap); } } } diff --git a/test/e2e/StrategyRewardsE2ETest.t.sol b/test/e2e/StrategyRewardsE2ETest.t.sol index 91303088..d43b89b1 100644 --- a/test/e2e/StrategyRewardsE2ETest.t.sol +++ b/test/e2e/StrategyRewardsE2ETest.t.sol @@ -2,17 +2,17 @@ pragma solidity ^0.8.0; import { - AggregationLayerVaultBase, - AggregationLayerVault, + EulerAggregationLayerBase, + EulerAggregationLayer, console2, EVault, IEVault, IRMTestDefault, TestERC20 -} from "../common/AggregationLayerVaultBase.t.sol"; +} from "../common/EulerAggregationLayerBase.t.sol"; import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol"; -contract StrategyRewardsE2ETest is AggregationLayerVaultBase { +contract StrategyRewardsE2ETest is EulerAggregationLayerBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -26,19 +26,19 @@ contract StrategyRewardsE2ETest is AggregationLayerVaultBase { function testOptInStrategyRewards() public { vm.prank(manager); - aggregationLayerVault.optInStrategyRewards(address(eTST)); + eulerAggregationLayer.optInStrategyRewards(address(eTST)); - assertTrue(eTST.balanceForwarderEnabled(address(aggregationLayerVault))); + assertTrue(eTST.balanceForwarderEnabled(address(eulerAggregationLayer))); } function testOptOutStrategyRewards() public { vm.prank(manager); - aggregationLayerVault.optInStrategyRewards(address(eTST)); - assertTrue(eTST.balanceForwarderEnabled(address(aggregationLayerVault))); + eulerAggregationLayer.optInStrategyRewards(address(eTST)); + assertTrue(eTST.balanceForwarderEnabled(address(eulerAggregationLayer))); vm.prank(manager); - aggregationLayerVault.optOutStrategyRewards(address(eTST)); + eulerAggregationLayer.optOutStrategyRewards(address(eTST)); - assertFalse(eTST.balanceForwarderEnabled(address(aggregationLayerVault))); + assertFalse(eTST.balanceForwarderEnabled(address(eulerAggregationLayer))); } } diff --git a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol index d363c4db..531d7253 100644 --- a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol +++ b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.0; import { - AggregationLayerVaultBase, - AggregationLayerVault, - IAggregationLayerVault -} from "../common/AggregationLayerVaultBase.t.sol"; + EulerAggregationLayerBase, + EulerAggregationLayer, + IEulerAggregationLayer +} from "../common/EulerAggregationLayerBase.t.sol"; -contract AdjustAllocationsPointsFuzzTest is AggregationLayerVaultBase { +contract AdjustAllocationsPointsFuzzTest is EulerAggregationLayerBase { function setUp() public virtual override { super.setUp(); @@ -18,23 +18,23 @@ contract AdjustAllocationsPointsFuzzTest is AggregationLayerVaultBase { function testFuzzAdjustAllocationPoints(uint256 _newAllocationPoints) public { _newAllocationPoints = bound(_newAllocationPoints, 0, type(uint120).max); - uint256 strategyAllocationPoints = (aggregationLayerVault.getStrategy(address(eTST))).allocationPoints; - uint256 totalAllocationPointsBefore = aggregationLayerVault.totalAllocationPoints(); + uint256 strategyAllocationPoints = (eulerAggregationLayer.getStrategy(address(eTST))).allocationPoints; + uint256 totalAllocationPointsBefore = eulerAggregationLayer.totalAllocationPoints(); uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); - aggregationLayerVault.adjustAllocationPoints(address(eTST), _newAllocationPoints); + eulerAggregationLayer.adjustAllocationPoints(address(eTST), _newAllocationPoints); - IAggregationLayerVault.Strategy memory strategy = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategy = eulerAggregationLayer.getStrategy(address(eTST)); if (_newAllocationPoints < strategyAllocationPoints) { assertEq( - aggregationLayerVault.totalAllocationPoints(), + eulerAggregationLayer.totalAllocationPoints(), totalAllocationPointsBefore - (strategyAllocationPoints - _newAllocationPoints) ); } else { assertEq( - aggregationLayerVault.totalAllocationPoints(), + eulerAggregationLayer.totalAllocationPoints(), totalAllocationPointsBefore + (_newAllocationPoints - strategyAllocationPoints) ); } diff --git a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol index dea9b6df..fd18fcc0 100644 --- a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol +++ b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {console2, AggregationLayerVaultBase, AggregationLayerVault} from "../common/AggregationLayerVaultBase.t.sol"; +import {console2, EulerAggregationLayerBase, EulerAggregationLayer} from "../common/EulerAggregationLayerBase.t.sol"; -contract DepositWithdrawMintBurnFuzzTest is AggregationLayerVaultBase { +contract DepositWithdrawMintBurnFuzzTest is EulerAggregationLayerBase { uint256 constant MAX_ALLOWED = type(uint256).max; function setUp() public virtual override { @@ -14,16 +14,16 @@ contract DepositWithdrawMintBurnFuzzTest is AggregationLayerVaultBase { // moch the scenario of _assets ownership assetTST.mint(user1, _assets); - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); _deposit(user1, _assets); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + _assets); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + _assets); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + _assets); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + _assets); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + _assets); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + _assets); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - _assets); } @@ -45,36 +45,36 @@ contract DepositWithdrawMintBurnFuzzTest is AggregationLayerVaultBase { vm.warp(block.timestamp + _timestampAfterDeposit); // fuzz partial & full withdraws - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 receiverAssetBalanceBefore = assetTST.balanceOf(_receiver); vm.startPrank(user1); - aggregationLayerVault.withdraw(_assetsToWithdraw, _receiver, user1); + eulerAggregationLayer.withdraw(_assetsToWithdraw, _receiver, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore - _assetsToWithdraw); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore - _assetsToWithdraw); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore - _assetsToWithdraw); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore - _assetsToWithdraw); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + _assetsToWithdraw); } function testFuzzMint(uint256 _shares) public { // moch the scenario of _assets ownership - uint256 assets = aggregationLayerVault.previewMint(_shares); + uint256 assets = eulerAggregationLayer.previewMint(_shares); assetTST.mint(user1, assets); - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); _mint(user1, assets, _shares); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + _shares); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + _shares); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + assets); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + _shares); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + _shares); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + assets); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - assets); } @@ -91,38 +91,38 @@ contract DepositWithdrawMintBurnFuzzTest is AggregationLayerVaultBase { _timestampAfterDeposit = bound(_timestampAfterDeposit, 0, 86400); // deposit - uint256 assetsToDeposit = aggregationLayerVault.previewMint(_sharesToMint); + uint256 assetsToDeposit = eulerAggregationLayer.previewMint(_sharesToMint); assetTST.mint(user1, assetsToDeposit); _mint(user1, assetsToDeposit, _sharesToMint); vm.warp(block.timestamp + _timestampAfterDeposit); // fuzz partial & full redeem - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 receiverAssetBalanceBefore = assetTST.balanceOf(_receiver); vm.startPrank(user1); - uint256 assetsToWithdraw = aggregationLayerVault.redeem(_sharesToRedeem, _receiver, user1); + uint256 assetsToWithdraw = eulerAggregationLayer.redeem(_sharesToRedeem, _receiver, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore - _sharesToRedeem); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore - _sharesToRedeem); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore - _sharesToRedeem); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore - _sharesToRedeem); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + assetsToWithdraw); } function _deposit(address _from, uint256 _assets) private { vm.startPrank(_from); - assetTST.approve(address(aggregationLayerVault), _assets); - aggregationLayerVault.deposit(_assets, _from); + assetTST.approve(address(eulerAggregationLayer), _assets); + eulerAggregationLayer.deposit(_assets, _from); vm.stopPrank(); } function _mint(address _from, uint256 _assets, uint256 _shares) private { vm.startPrank(_from); - assetTST.approve(address(aggregationLayerVault), _assets); - aggregationLayerVault.mint(_shares, _from); + assetTST.approve(address(eulerAggregationLayer), _assets); + eulerAggregationLayer.mint(_shares, _from); vm.stopPrank(); } } diff --git a/test/unit/AddStrategyTest.t.sol b/test/unit/AddStrategyTest.t.sol index 0efc9e8c..0e00a089 100644 --- a/test/unit/AddStrategyTest.t.sol +++ b/test/unit/AddStrategyTest.t.sol @@ -1,22 +1,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {AggregationLayerVaultBase, AggregationLayerVault} from "../common/AggregationLayerVaultBase.t.sol"; +import {EulerAggregationLayerBase, EulerAggregationLayer} from "../common/EulerAggregationLayerBase.t.sol"; -contract AddStrategyTest is AggregationLayerVaultBase { +contract AddStrategyTest is EulerAggregationLayerBase { function setUp() public virtual override { super.setUp(); } function testAddStrategy() public { uint256 allocationPoints = 500e18; - uint256 totalAllocationPointsBefore = aggregationLayerVault.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = eulerAggregationLayer.totalAllocationPoints(); assertEq(_getWithdrawalQueueLength(), 0); _addStrategy(manager, address(eTST), allocationPoints); - assertEq(aggregationLayerVault.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); + assertEq(eulerAggregationLayer.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); assertEq(_getWithdrawalQueueLength(), 1); } @@ -40,13 +40,13 @@ contract AddStrategyTest is AggregationLayerVaultBase { function testAddStrategy_AlreadyAddedStrategy() public { uint256 allocationPoints = 500e18; - uint256 totalAllocationPointsBefore = aggregationLayerVault.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = eulerAggregationLayer.totalAllocationPoints(); assertEq(_getWithdrawalQueueLength(), 0); _addStrategy(manager, address(eTST), allocationPoints); - assertEq(aggregationLayerVault.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); + assertEq(eulerAggregationLayer.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); assertEq(_getWithdrawalQueueLength(), 1); vm.expectRevert(); diff --git a/test/unit/AdjustAllocationPointsTest.t.sol b/test/unit/AdjustAllocationPointsTest.t.sol index 54824568..2b76fc00 100644 --- a/test/unit/AdjustAllocationPointsTest.t.sol +++ b/test/unit/AdjustAllocationPointsTest.t.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.0; import { - AggregationLayerVaultBase, - AggregationLayerVault, - IAggregationLayerVault, + EulerAggregationLayerBase, + EulerAggregationLayer, + IEulerAggregationLayer, ErrorsLib -} from "../common/AggregationLayerVaultBase.t.sol"; +} from "../common/EulerAggregationLayerBase.t.sol"; -contract AdjustAllocationsPointsTest is AggregationLayerVaultBase { +contract AdjustAllocationsPointsTest is EulerAggregationLayerBase { uint256 initialStrategyAllocationPoints = 500e18; function setUp() public virtual override { @@ -19,16 +19,16 @@ contract AdjustAllocationsPointsTest is AggregationLayerVaultBase { function testAdjustAllocationPoints() public { uint256 newAllocationPoints = 859e18; - uint256 totalAllocationPointsBefore = aggregationLayerVault.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = eulerAggregationLayer.totalAllocationPoints(); uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); - aggregationLayerVault.adjustAllocationPoints(address(eTST), newAllocationPoints); + eulerAggregationLayer.adjustAllocationPoints(address(eTST), newAllocationPoints); - IAggregationLayerVault.Strategy memory strategy = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategy = eulerAggregationLayer.getStrategy(address(eTST)); assertEq( - aggregationLayerVault.totalAllocationPoints(), + eulerAggregationLayer.totalAllocationPoints(), totalAllocationPointsBefore + (newAllocationPoints - initialStrategyAllocationPoints) ); assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore); @@ -40,7 +40,7 @@ contract AdjustAllocationsPointsTest is AggregationLayerVaultBase { vm.startPrank(deployer); vm.expectRevert(); - aggregationLayerVault.adjustAllocationPoints(address(eTST), newAllocationPoints); + eulerAggregationLayer.adjustAllocationPoints(address(eTST), newAllocationPoints); vm.stopPrank(); } @@ -49,7 +49,7 @@ contract AdjustAllocationsPointsTest is AggregationLayerVaultBase { vm.startPrank(manager); vm.expectRevert(ErrorsLib.InactiveStrategy.selector); - aggregationLayerVault.adjustAllocationPoints(address(eTST2), newAllocationPoints); + eulerAggregationLayer.adjustAllocationPoints(address(eTST2), newAllocationPoints); vm.stopPrank(); } } diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index 0924e0f6..40dd9f8e 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.0; import { - AggregationLayerVaultBase, - AggregationLayerVault, + EulerAggregationLayerBase, + EulerAggregationLayer, console2, EVault, - IAggregationLayerVault -} from "../common/AggregationLayerVaultBase.t.sol"; + IEulerAggregationLayer +} from "../common/EulerAggregationLayerBase.t.sol"; -contract GulpTest is AggregationLayerVaultBase { +contract GulpTest is EulerAggregationLayerBase { uint256 user1InitialBalance = 100000e18; uint256 amountToDeposit = 10000e18; @@ -23,142 +23,142 @@ contract GulpTest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedStrategyCash); } } function testGulpAfterNegativeYieldEqualToInterestLeft() public { - aggregationLayerVault.gulp(); - AggregationLayerVault.AggregationVaultSavingRate memory ers = - aggregationLayerVault.getAggregationVaultSavingRate(); - assertEq(aggregationLayerVault.interestAccrued(), 0); + eulerAggregationLayer.gulp(); + EulerAggregationLayer.AggregationVaultSavingRate memory ers = + eulerAggregationLayer.getAggregationVaultSavingRate(); + assertEq(eulerAggregationLayer.interestAccrued(), 0); assertEq(ers.interestLeft, 0); vm.warp(block.timestamp + 2 days); - aggregationLayerVault.gulp(); - assertEq(aggregationLayerVault.interestAccrued(), 0); + eulerAggregationLayer.gulp(); + assertEq(eulerAggregationLayer.interestAccrued(), 0); vm.warp(block.timestamp + 1 days); - assertEq(aggregationLayerVault.interestAccrued(), 0); + assertEq(eulerAggregationLayer.interestAccrued(), 0); uint256 yield; { - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(aggregationLayerVault)); + eTST.skim(type(uint256).max, address(eulerAggregationLayer)); } vm.prank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); - assertEq(aggregationLayerVault.interestAccrued(), 0); + assertEq(eulerAggregationLayer.interestAccrued(), 0); vm.warp(block.timestamp + 1 days); // interest per day 23.809523809523 - assertEq(aggregationLayerVault.interestAccrued(), 23809523809523809523); - aggregationLayerVault.gulp(); - ers = aggregationLayerVault.getAggregationVaultSavingRate(); + assertEq(eulerAggregationLayer.interestAccrued(), 23809523809523809523); + eulerAggregationLayer.gulp(); + ers = eulerAggregationLayer.getAggregationVaultSavingRate(); assertEq(ers.interestLeft, yield - 23809523809523809523); // move close to end of smearing vm.warp(block.timestamp + 11 days); - aggregationLayerVault.gulp(); - ers = aggregationLayerVault.getAggregationVaultSavingRate(); + eulerAggregationLayer.gulp(); + ers = eulerAggregationLayer.getAggregationVaultSavingRate(); // mock a decrease of strategy balance by ers.interestLeft - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - ers.interestLeft; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.balanceOf.selector, address(eulerAggregationLayer)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); vm.prank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); } function testGulpAfterNegativeYieldBiggerThanInterestLeft() public { - aggregationLayerVault.gulp(); - AggregationLayerVault.AggregationVaultSavingRate memory ers = - aggregationLayerVault.getAggregationVaultSavingRate(); - assertEq(aggregationLayerVault.interestAccrued(), 0); + eulerAggregationLayer.gulp(); + EulerAggregationLayer.AggregationVaultSavingRate memory ers = + eulerAggregationLayer.getAggregationVaultSavingRate(); + assertEq(eulerAggregationLayer.interestAccrued(), 0); assertEq(ers.interestLeft, 0); vm.warp(block.timestamp + 2 days); - aggregationLayerVault.gulp(); - assertEq(aggregationLayerVault.interestAccrued(), 0); + eulerAggregationLayer.gulp(); + assertEq(eulerAggregationLayer.interestAccrued(), 0); vm.warp(block.timestamp + 1 days); - assertEq(aggregationLayerVault.interestAccrued(), 0); + assertEq(eulerAggregationLayer.interestAccrued(), 0); uint256 yield; { - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(aggregationLayerVault)); + eTST.skim(type(uint256).max, address(eulerAggregationLayer)); } vm.prank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); - assertEq(aggregationLayerVault.interestAccrued(), 0); + assertEq(eulerAggregationLayer.interestAccrued(), 0); vm.warp(block.timestamp + 1 days); // interest per day 23.809523809523 - assertEq(aggregationLayerVault.interestAccrued(), 23809523809523809523); - aggregationLayerVault.gulp(); - ers = aggregationLayerVault.getAggregationVaultSavingRate(); + assertEq(eulerAggregationLayer.interestAccrued(), 23809523809523809523); + eulerAggregationLayer.gulp(); + ers = eulerAggregationLayer.getAggregationVaultSavingRate(); assertEq(ers.interestLeft, yield - 23809523809523809523); // move close to end of smearing vm.warp(block.timestamp + 11 days); - aggregationLayerVault.gulp(); - ers = aggregationLayerVault.getAggregationVaultSavingRate(); + eulerAggregationLayer.gulp(); + ers = eulerAggregationLayer.getAggregationVaultSavingRate(); // mock a decrease of strategy balance by ers.interestLeft - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - (ers.interestLeft * 2); vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.balanceOf.selector, address(eulerAggregationLayer)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); vm.prank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); } } diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index 34803cab..48972e8d 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.0; import { - AggregationLayerVaultBase, - AggregationLayerVault, + EulerAggregationLayerBase, + EulerAggregationLayer, console2, EVault, - IAggregationLayerVault -} from "../common/AggregationLayerVaultBase.t.sol"; + IEulerAggregationLayer +} from "../common/EulerAggregationLayerBase.t.sol"; -contract HarvestTest is AggregationLayerVaultBase { +contract HarvestTest is EulerAggregationLayerBase { uint256 user1InitialBalance = 100000e18; uint256 amountToDeposit = 10000e18; @@ -23,74 +23,74 @@ contract HarvestTest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() - * strategyBefore.allocationPoints / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedStrategyCash); } } function testHarvestWithPositiveYield() public { // no yield increase - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - uint256 totalAllocatedBefore = aggregationLayerVault.totalAllocated(); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + uint256 totalAllocatedBefore = eulerAggregationLayer.totalAllocated(); - assertTrue(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))) == strategyBefore.allocated); + assertTrue(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))) == strategyBefore.allocated); vm.prank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated); - assertEq(aggregationLayerVault.totalAllocated(), totalAllocatedBefore); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated); + assertEq(eulerAggregationLayer.totalAllocated(), totalAllocatedBefore); vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), abi.encode(aggrCurrentStrategyBalance * 11e17 / 1e18) ); - uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); assertTrue(expectedAllocated > strategyBefore.allocated); vm.prank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedAllocated); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedAllocated); assertEq( - aggregationLayerVault.totalAllocated(), + eulerAggregationLayer.totalAllocated(), totalAllocatedBefore + (expectedAllocated - strategyBefore.allocated) ); } @@ -99,58 +99,58 @@ contract HarvestTest is AggregationLayerVaultBase { vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), abi.encode(aggrCurrentStrategyBalance * 9e17 / 1e18) ); - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); assertTrue(expectedAllocated < strategyBefore.allocated); uint256 expectedLoss = strategyBefore.allocated - expectedAllocated; - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); vm.prank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); // check that loss socialized from the deposits - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedLoss); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedLoss); } function testHarvestNegativeYieldSingleUser() public { vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); assertTrue(expectedAllocated < strategyBefore.allocated); - uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(aggregationLayerVault)); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(eulerAggregationLayer)); - uint256 user1SharesBefore = aggregationLayerVault.balanceOf(user1); - uint256 user1SocializedLoss = user1SharesBefore * negativeYield / aggregationLayerVault.totalSupply(); + uint256 user1SharesBefore = eulerAggregationLayer.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / eulerAggregationLayer.totalSupply(); uint256 expectedUser1Assets = - user1SharesBefore * amountToDeposit / aggregationLayerVault.totalSupply() - user1SocializedLoss; + user1SharesBefore * amountToDeposit / eulerAggregationLayer.totalSupply() - user1SocializedLoss; uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); vm.stopPrank(); - uint256 user1SharesAfter = aggregationLayerVault.balanceOf(user1); - uint256 user1AssetsAfter = aggregationLayerVault.convertToAssets(user1SharesAfter); + uint256 user1SharesAfter = eulerAggregationLayer.balanceOf(user1); + uint256 user1AssetsAfter = eulerAggregationLayer.convertToAssets(user1SharesAfter); assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets, 1); } @@ -161,71 +161,71 @@ contract HarvestTest is AggregationLayerVaultBase { // deposit into aggregator { vm.startPrank(user2); - assetTST.approve(address(aggregationLayerVault), user2InitialBalance); - aggregationLayerVault.deposit(user2InitialBalance, user2); + assetTST.approve(address(eulerAggregationLayer), user2InitialBalance); + eulerAggregationLayer.deposit(user2InitialBalance, user2); vm.stopPrank(); } vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); assertTrue(expectedAllocated < strategyBefore.allocated); - uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(aggregationLayerVault)); - uint256 user1SharesBefore = aggregationLayerVault.balanceOf(user1); - uint256 user1SocializedLoss = user1SharesBefore * negativeYield / aggregationLayerVault.totalSupply(); - uint256 user2SharesBefore = aggregationLayerVault.balanceOf(user2); - uint256 user2SocializedLoss = user2SharesBefore * negativeYield / aggregationLayerVault.totalSupply(); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(eulerAggregationLayer)); + uint256 user1SharesBefore = eulerAggregationLayer.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / eulerAggregationLayer.totalSupply(); + uint256 user2SharesBefore = eulerAggregationLayer.balanceOf(user2); + uint256 user2SocializedLoss = user2SharesBefore * negativeYield / eulerAggregationLayer.totalSupply(); uint256 expectedUser1Assets = user1SharesBefore * (amountToDeposit + user2InitialBalance) - / aggregationLayerVault.totalSupply() - user1SocializedLoss; + / eulerAggregationLayer.totalSupply() - user1SocializedLoss; uint256 expectedUser2Assets = user2SharesBefore * (amountToDeposit + user2InitialBalance) - / aggregationLayerVault.totalSupply() - user2SocializedLoss; + / eulerAggregationLayer.totalSupply() - user2SocializedLoss; vm.prank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); - uint256 user1SharesAfter = aggregationLayerVault.balanceOf(user1); - uint256 user1AssetsAfter = aggregationLayerVault.convertToAssets(user1SharesAfter); - uint256 user2SharesAfter = aggregationLayerVault.balanceOf(user2); - uint256 user2AssetsAfter = aggregationLayerVault.convertToAssets(user2SharesAfter); + uint256 user1SharesAfter = eulerAggregationLayer.balanceOf(user1); + uint256 user1AssetsAfter = eulerAggregationLayer.convertToAssets(user1SharesAfter); + uint256 user2SharesAfter = eulerAggregationLayer.balanceOf(user2); + uint256 user2AssetsAfter = eulerAggregationLayer.convertToAssets(user2SharesAfter); assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets, 1); assertApproxEqAbs(user2AssetsAfter, expectedUser2Assets, 1); } function testHarvestWhenInteresetLeftGreaterThanLoss() public { - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - uint256 totalAllocatedBefore = aggregationLayerVault.totalAllocated(); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + uint256 totalAllocatedBefore = eulerAggregationLayer.totalAllocated(); vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(aggregationLayerVault)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), abi.encode(aggrCurrentStrategyBalance * 11e17 / 1e18) ); - uint256 expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); assertTrue(expectedAllocated > strategyBefore.allocated); vm.prank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, expectedAllocated); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedAllocated); assertEq( - aggregationLayerVault.totalAllocated(), + eulerAggregationLayer.totalAllocated(), totalAllocatedBefore + (expectedAllocated - strategyBefore.allocated) ); @@ -235,27 +235,27 @@ contract HarvestTest is AggregationLayerVaultBase { uint256 aggrCurrentStrategyBalanceAfterNegYield = expectedAllocated * 98e16 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(aggregationLayerVault)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); - expectedAllocated = eTST.maxWithdraw(address(aggregationLayerVault)); + strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); assertTrue(expectedAllocated < strategyBefore.allocated); - uint256 user1SharesBefore = aggregationLayerVault.balanceOf(user1); - uint256 expectedUser1Assets = user1SharesBefore * amountToDeposit / aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); - uint256 interestToBeAccrued = aggregationLayerVault.interestAccrued(); + uint256 user1SharesBefore = eulerAggregationLayer.balanceOf(user1); + uint256 expectedUser1Assets = user1SharesBefore * amountToDeposit / eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 interestToBeAccrued = eulerAggregationLayer.interestAccrued(); vm.startPrank(user1); - aggregationLayerVault.harvest(); + eulerAggregationLayer.harvest(); vm.stopPrank(); - uint256 user1SharesAfter = aggregationLayerVault.balanceOf(user1); - uint256 user1AssetsAfter = aggregationLayerVault.convertToAssets(user1SharesAfter); + uint256 user1SharesAfter = eulerAggregationLayer.balanceOf(user1); + uint256 user1AssetsAfter = eulerAggregationLayer.convertToAssets(user1SharesAfter); assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets + interestToBeAccrued, 1); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + interestToBeAccrued); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + interestToBeAccrued); } } diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index 4abbfe55..564d3358 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -2,17 +2,17 @@ pragma solidity ^0.8.0; import { - AggregationLayerVaultBase, - AggregationLayerVault, + EulerAggregationLayerBase, + EulerAggregationLayer, console2, EVault, IEVault, IRMTestDefault, TestERC20, - IAggregationLayerVault -} from "../common/AggregationLayerVaultBase.t.sol"; + IEulerAggregationLayer +} from "../common/EulerAggregationLayerBase.t.sol"; -contract RebalanceTest is AggregationLayerVaultBase { +contract RebalanceTest is EulerAggregationLayerBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -29,40 +29,40 @@ contract RebalanceTest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() * strategyBefore.allocationPoints - / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() * strategyBefore.allocationPoints + / eulerAggregationLayer.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); + assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); assertEq( - (aggregationLayerVault.getStrategy(address(eTST))).allocated, + (eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash ); } @@ -72,46 +72,46 @@ contract RebalanceTest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() * strategyBefore.allocationPoints - / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() * strategyBefore.allocationPoints + / eulerAggregationLayer.totalAllocationPoints(); uint256 expectedToDeposit = expectedStrategyCash - strategyBefore.allocated; uint256 eTSTMaxDeposit = expectedToDeposit * 7e17 / 1e18; // mock max deposit vm.mockCall( - address(eTST), abi.encodeCall(eTST.maxDeposit, (address(aggregationLayerVault))), abi.encode(eTSTMaxDeposit) + address(eTST), abi.encodeCall(eTST.maxDeposit, (address(eulerAggregationLayer))), abi.encode(eTSTMaxDeposit) ); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - assertEq(aggregationLayerVault.totalAllocated(), eTSTMaxDeposit); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), eTSTMaxDeposit); + assertEq(eulerAggregationLayer.totalAllocated(), eTSTMaxDeposit); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), eTSTMaxDeposit); assertEq( - (aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated + eTSTMaxDeposit + (eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated + eTSTMaxDeposit ); } @@ -120,19 +120,19 @@ contract RebalanceTest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -141,7 +141,7 @@ contract RebalanceTest is AggregationLayerVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); // create new strategy & add it IEVault eTSTsecondary; @@ -158,33 +158,33 @@ contract RebalanceTest is AggregationLayerVaultBase { // rebalance into eTSTsecondary vm.warp(block.timestamp + 86400); { - IAggregationLayerVault.Strategy memory strategyBefore = - aggregationLayerVault.getStrategy(address(eTSTsecondary)); + IEulerAggregationLayer.Strategy memory strategyBefore = + eulerAggregationLayer.getStrategy(address(eTSTsecondary)); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated ); - uint256 targetCash = aggregationLayerVault.totalAssetsAllocatable() - * aggregationLayerVault.getStrategy(address(0)).allocationPoints - / aggregationLayerVault.totalAllocationPoints(); + uint256 targetCash = eulerAggregationLayer.totalAssetsAllocatable() + * eulerAggregationLayer.getStrategy(address(0)).allocationPoints + / eulerAggregationLayer.totalAllocationPoints(); uint256 currentCash = - aggregationLayerVault.totalAssetsAllocatable() - aggregationLayerVault.totalAllocated(); + eulerAggregationLayer.totalAssetsAllocatable() - eulerAggregationLayer.totalAllocated(); uint256 expectedStrategyCash = currentCash - targetCash; vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTSTsecondary); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - // assertEq(aggregationLayerVault.totalAllocated(), eTSTsecondaryMaxDeposit); + // assertEq(eulerAggregationLayer.totalAllocated(), eTSTsecondaryMaxDeposit); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(aggregationLayerVault))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash ); assertEq( - (aggregationLayerVault.getStrategy(address(eTSTsecondary))).allocated, + (eulerAggregationLayer.getStrategy(address(eTSTsecondary))).allocated, strategyBefore.allocated + expectedStrategyCash ); } @@ -195,42 +195,42 @@ contract RebalanceTest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); uint256 eTSTMaxDeposit = 0; // mock max deposit vm.mockCall( - address(eTST), abi.encodeCall(eTST.maxDeposit, (address(aggregationLayerVault))), abi.encode(eTSTMaxDeposit) + address(eTST), abi.encodeCall(eTST.maxDeposit, (address(eulerAggregationLayer))), abi.encode(eTSTMaxDeposit) ); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - assertEq(aggregationLayerVault.totalAllocated(), strategyBefore.allocated); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); - assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated); + assertEq(eulerAggregationLayer.totalAllocated(), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated); } function testRebalanceByWithdrawing() public { @@ -238,19 +238,19 @@ contract RebalanceTest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -259,30 +259,30 @@ contract RebalanceTest is AggregationLayerVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; vm.prank(manager); - aggregationLayerVault.adjustAllocationPoints(address(eTST), newAllocationPoints); + eulerAggregationLayer.adjustAllocationPoints(address(eTST), newAllocationPoints); vm.warp(block.timestamp + 86400); - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() * strategyBefore.allocationPoints - / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() * strategyBefore.allocationPoints + / eulerAggregationLayer.totalAllocationPoints(); vm.prank(user1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - assertEq(aggregationLayerVault.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), expectedStrategyCash); + assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); assertEq( - (aggregationLayerVault.getStrategy(address(eTST))).allocated, + (eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated - (strategyBefore.allocated - expectedStrategyCash) ); } @@ -292,19 +292,19 @@ contract RebalanceTest is AggregationLayerVaultBase { // deposit into aggregator { - uint256 balanceBefore = aggregationLayerVault.balanceOf(user1); - uint256 totalSupplyBefore = aggregationLayerVault.totalSupply(); - uint256 totalAssetsDepositedBefore = aggregationLayerVault.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(aggregationLayerVault), amountToDeposit); - aggregationLayerVault.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationLayer), amountToDeposit); + eulerAggregationLayer.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(aggregationLayerVault.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(aggregationLayerVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -313,38 +313,38 @@ contract RebalanceTest is AggregationLayerVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; vm.prank(manager); - aggregationLayerVault.adjustAllocationPoints(address(eTST), newAllocationPoints); + eulerAggregationLayer.adjustAllocationPoints(address(eTST), newAllocationPoints); vm.warp(block.timestamp + 86400); - IAggregationLayerVault.Strategy memory strategyBefore = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - uint256 expectedStrategyCash = aggregationLayerVault.totalAssetsAllocatable() * strategyBefore.allocationPoints - / aggregationLayerVault.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() * strategyBefore.allocationPoints + / eulerAggregationLayer.totalAllocationPoints(); uint256 expectedToWithdraw = strategyBefore.allocated - expectedStrategyCash; uint256 eTSTMaxWithdraw = expectedToWithdraw * 7e17 / 1e18; // mock max withdraw vm.mockCall( address(eTST), - abi.encodeCall(eTST.maxWithdraw, (address(aggregationLayerVault))), + abi.encodeCall(eTST.maxWithdraw, (address(eulerAggregationLayer))), abi.encode(eTSTMaxWithdraw) ); vm.prank(user1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(aggregationLayerVault), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); - // assertEq(aggregationLayerVault.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); + // assertEq(eulerAggregationLayer.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); // assertEq( - // eTST.convertToAssets(eTST.balanceOf(address(aggregationLayerVault))), strategyBefore.allocated - eTSTMaxWithdraw + // eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated - eTSTMaxWithdraw // ); - // assertEq((aggregationLayerVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated - eTSTMaxWithdraw); + // assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated - eTSTMaxWithdraw); } } diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index e92cb8c8..6dfb15ab 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.0; import { - AggregationLayerVaultBase, - AggregationLayerVault, + EulerAggregationLayerBase, + EulerAggregationLayer, IEVault, - IAggregationLayerVault -} from "../common/AggregationLayerVaultBase.t.sol"; + IEulerAggregationLayer +} from "../common/EulerAggregationLayerBase.t.sol"; -contract RemoveStrategyTest is AggregationLayerVaultBase { +contract RemoveStrategyTest is EulerAggregationLayerBase { uint256 strategyAllocationPoints; IEVault anotherStrategy; @@ -21,17 +21,17 @@ contract RemoveStrategyTest is AggregationLayerVaultBase { } function testRemoveStrategy() public { - uint256 totalAllocationPointsBefore = aggregationLayerVault.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = eulerAggregationLayer.totalAllocationPoints(); uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); - aggregationLayerVault.removeStrategy(address(eTST)); + eulerAggregationLayer.removeStrategy(address(eTST)); - IAggregationLayerVault.Strategy memory strategyAfter = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyAfter = eulerAggregationLayer.getStrategy(address(eTST)); assertEq(strategyAfter.active, false); assertEq(strategyAfter.allocationPoints, 0); - assertEq(aggregationLayerVault.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); + assertEq(eulerAggregationLayer.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore - 1); } @@ -41,17 +41,17 @@ contract RemoveStrategyTest is AggregationLayerVaultBase { ); _addStrategy(manager, address(anotherStrategy), strategyAllocationPoints); - uint256 totalAllocationPointsBefore = aggregationLayerVault.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = eulerAggregationLayer.totalAllocationPoints(); uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); - aggregationLayerVault.removeStrategy(address(eTST)); + eulerAggregationLayer.removeStrategy(address(eTST)); - IAggregationLayerVault.Strategy memory strategyAfter = aggregationLayerVault.getStrategy(address(eTST)); + IEulerAggregationLayer.Strategy memory strategyAfter = eulerAggregationLayer.getStrategy(address(eTST)); assertEq(strategyAfter.active, false); assertEq(strategyAfter.allocationPoints, 0); - assertEq(aggregationLayerVault.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); + assertEq(eulerAggregationLayer.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore - 1); } @@ -75,7 +75,7 @@ contract RemoveStrategyTest is AggregationLayerVaultBase { assertEq(withdrawalQueue[3], strategy3); vm.prank(manager); - aggregationLayerVault.removeStrategy(strategy2); + eulerAggregationLayer.removeStrategy(strategy2); withdrawalQueue = _getWithdrawalQueue(); assertEq(withdrawalQueue.length, 3); @@ -84,7 +84,7 @@ contract RemoveStrategyTest is AggregationLayerVaultBase { assertEq(withdrawalQueue[2], strategy3); vm.prank(manager); - aggregationLayerVault.removeStrategy(strategy3); + eulerAggregationLayer.removeStrategy(strategy3); withdrawalQueue = _getWithdrawalQueue(); assertEq(withdrawalQueue.length, 2); @@ -92,14 +92,14 @@ contract RemoveStrategyTest is AggregationLayerVaultBase { assertEq(withdrawalQueue[1], strategy1); vm.prank(manager); - aggregationLayerVault.removeStrategy(address(eTST)); + eulerAggregationLayer.removeStrategy(address(eTST)); withdrawalQueue = _getWithdrawalQueue(); assertEq(withdrawalQueue.length, 1); assertEq(withdrawalQueue[0], strategy1); vm.prank(manager); - aggregationLayerVault.removeStrategy(strategy1); + eulerAggregationLayer.removeStrategy(strategy1); withdrawalQueue = _getWithdrawalQueue(); assertEq(withdrawalQueue.length, 0); @@ -108,12 +108,12 @@ contract RemoveStrategyTest is AggregationLayerVaultBase { function testRemoveStrategy_fromUnauthorized() public { vm.prank(deployer); vm.expectRevert(); - aggregationLayerVault.removeStrategy(address(eTST)); + eulerAggregationLayer.removeStrategy(address(eTST)); } function testRemoveStrategy_AlreadyRemoved() public { vm.prank(manager); vm.expectRevert(); - aggregationLayerVault.removeStrategy(address(eTST2)); + eulerAggregationLayer.removeStrategy(address(eTST2)); } } diff --git a/test/unit/ReorderWithdrawalQueueTest.t.sol b/test/unit/ReorderWithdrawalQueueTest.t.sol index 3251194e..5f684df6 100644 --- a/test/unit/ReorderWithdrawalQueueTest.t.sol +++ b/test/unit/ReorderWithdrawalQueueTest.t.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.0; import { - AggregationLayerVaultBase, - AggregationLayerVault, + EulerAggregationLayerBase, + EulerAggregationLayer, IEVault, WithdrawalQueue -} from "../common/AggregationLayerVaultBase.t.sol"; +} from "../common/EulerAggregationLayerBase.t.sol"; -contract ReorderWithdrawalQueueTest is AggregationLayerVaultBase { +contract ReorderWithdrawalQueueTest is EulerAggregationLayerBase { uint256 eTSTAllocationPoints = 500e18; uint256 eTSTsecondaryAllocationPoints = 700e18; @@ -30,18 +30,18 @@ contract ReorderWithdrawalQueueTest is AggregationLayerVaultBase { } function testReorderWithdrawalQueue() public { - assertEq(aggregationLayerVault.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTAllocationPoints); + assertEq(eulerAggregationLayer.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTAllocationPoints); assertEq( - aggregationLayerVault.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTsecondaryAllocationPoints + eulerAggregationLayer.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTsecondaryAllocationPoints ); vm.prank(manager); withdrawalQueue.reorderWithdrawalQueue(0, 1); assertEq( - aggregationLayerVault.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTsecondaryAllocationPoints + eulerAggregationLayer.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTsecondaryAllocationPoints ); - assertEq(aggregationLayerVault.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTAllocationPoints); + assertEq(eulerAggregationLayer.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTAllocationPoints); } function testReorderWithdrawalQueueWhenOutOfBounds() public { From 49fc1ebb451497617962a905d51c4803e32c07b8 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:02:51 +0300 Subject: [PATCH 198/316] separate core from plugin --- src/{ => core}/Dispatch.sol | 11 ++++++++--- src/{ => core}/EulerAggregationLayer.sol | 2 +- src/{ => core}/EulerAggregationLayerFactory.sol | 2 +- src/{ => core/common}/Shared.sol | 6 +++--- src/{ => core}/interface/IBalanceForwarder.sol | 0 .../interface/IEulerAggregationLayer.sol | 0 src/{ => core}/interface/IWithdrawalQueue.sol | 0 src/{ => core}/lib/ErrorsLib.sol | 0 src/{ => core}/lib/EventsLib.sol | 0 src/{ => core}/lib/HooksLib.sol | 0 src/{ => core}/lib/StorageLib.sol | 5 ----- src/{ => core}/module/AllocationPoints.sol | 2 +- src/{ => core}/module/Fee.sol | 2 +- src/{ => core}/module/Hooks.sol | 2 +- src/{ => core}/module/Rewards.sol | 2 +- src/plugin/Rebalancer.sol | 2 +- src/plugin/WithdrawalQueue.sol | 4 ++-- test/common/EulerAggregationLayerBase.t.sol | 16 ++++++++-------- test/e2e/PerformanceFeeE2ETest.t.sol | 9 ++++----- test/e2e/StrategyCapE2ETest.t.sol | 4 ++-- test/unit/HarvestTest.t.sol | 1 - test/unit/RebalanceTest.t.sol | 3 +-- 22 files changed, 35 insertions(+), 38 deletions(-) rename src/{ => core}/Dispatch.sol (80%) rename src/{ => core}/EulerAggregationLayer.sol (99%) rename src/{ => core}/EulerAggregationLayerFactory.sol (98%) rename src/{ => core/common}/Shared.sol (95%) rename src/{ => core}/interface/IBalanceForwarder.sol (100%) rename src/{ => core}/interface/IEulerAggregationLayer.sol (100%) rename src/{ => core}/interface/IWithdrawalQueue.sol (100%) rename src/{ => core}/lib/ErrorsLib.sol (100%) rename src/{ => core}/lib/EventsLib.sol (100%) rename src/{ => core}/lib/HooksLib.sol (100%) rename src/{ => core}/lib/StorageLib.sol (99%) rename src/{ => core}/module/AllocationPoints.sol (99%) rename src/{ => core}/module/Fee.sol (97%) rename src/{ => core}/module/Hooks.sol (96%) rename src/{ => core}/module/Rewards.sol (99%) diff --git a/src/Dispatch.sol b/src/core/Dispatch.sol similarity index 80% rename from src/Dispatch.sol rename to src/core/Dispatch.sol index 9d94fe20..78534a08 100644 --- a/src/Dispatch.sol +++ b/src/core/Dispatch.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; //contracts -import {Shared} from "./Shared.sol"; +import {Shared} from "./common/Shared.sol"; import {HooksModule} from "./module/Hooks.sol"; import {RewardsModule} from "./module/Rewards.sol"; @@ -17,6 +17,11 @@ abstract contract Dispatch is RewardsModule, HooksModule { address public immutable MODULE_FEE; address public immutable MODULE_ALLOCATION_POINTS; + /// @dev Constructor. + /// @param _rewardsModule Address of Rewards module. + /// @param _hooksModule Address of Hooks module. + /// @param _feeModule Address of Fee module. + /// @param _allocationPointsModule Address of AllocationPoints module. constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) Shared() { @@ -29,10 +34,10 @@ abstract contract Dispatch is RewardsModule, HooksModule { // Modifier proxies the function call to a module and low-level returns the result modifier use(address module) { _; // when using the modifier, it is assumed the function body is empty. - delegateToModule(module); + _delegateToModule(module); } - function delegateToModule(address module) private { + function _delegateToModule(address module) private { assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), module, 0, calldatasize(), 0, 0) diff --git a/src/EulerAggregationLayer.sol b/src/core/EulerAggregationLayer.sol similarity index 99% rename from src/EulerAggregationLayer.sol rename to src/core/EulerAggregationLayer.sol index bdb46af3..ed321133 100644 --- a/src/EulerAggregationLayer.sol +++ b/src/core/EulerAggregationLayer.sol @@ -15,7 +15,7 @@ import { } from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; -import {Shared} from "./Shared.sol"; +import {Shared} from "./common/Shared.sol"; import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgradeable.sol"; // libs import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; diff --git a/src/EulerAggregationLayerFactory.sol b/src/core/EulerAggregationLayerFactory.sol similarity index 98% rename from src/EulerAggregationLayerFactory.sol rename to src/core/EulerAggregationLayerFactory.sol index fb38cb20..3d0c06e2 100644 --- a/src/EulerAggregationLayerFactory.sol +++ b/src/core/EulerAggregationLayerFactory.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import {Rewards} from "./module/Rewards.sol"; import {Hooks} from "./module/Hooks.sol"; import {Fee} from "./module/Fee.sol"; -import {WithdrawalQueue} from "./plugin/WithdrawalQueue.sol"; +import {WithdrawalQueue} from "../plugin/WithdrawalQueue.sol"; import {EulerAggregationLayer, IEulerAggregationLayer} from "./EulerAggregationLayer.sol"; // libs import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; diff --git a/src/Shared.sol b/src/core/common/Shared.sol similarity index 95% rename from src/Shared.sol rename to src/core/common/Shared.sol index 22a2411a..cd45771f 100644 --- a/src/Shared.sol +++ b/src/core/common/Shared.sol @@ -5,9 +5,9 @@ pragma solidity ^0.8.0; import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; // libs -import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; -import {HooksLib} from "./lib/HooksLib.sol"; -import {ErrorsLib as Errors} from "./lib/ErrorsLib.sol"; +import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; +import {HooksLib} from "../lib/HooksLib.sol"; +import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; /// @title Shared contract /// @custom:security-contact security@euler.xyz diff --git a/src/interface/IBalanceForwarder.sol b/src/core/interface/IBalanceForwarder.sol similarity index 100% rename from src/interface/IBalanceForwarder.sol rename to src/core/interface/IBalanceForwarder.sol diff --git a/src/interface/IEulerAggregationLayer.sol b/src/core/interface/IEulerAggregationLayer.sol similarity index 100% rename from src/interface/IEulerAggregationLayer.sol rename to src/core/interface/IEulerAggregationLayer.sol diff --git a/src/interface/IWithdrawalQueue.sol b/src/core/interface/IWithdrawalQueue.sol similarity index 100% rename from src/interface/IWithdrawalQueue.sol rename to src/core/interface/IWithdrawalQueue.sol diff --git a/src/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol similarity index 100% rename from src/lib/ErrorsLib.sol rename to src/core/lib/ErrorsLib.sol diff --git a/src/lib/EventsLib.sol b/src/core/lib/EventsLib.sol similarity index 100% rename from src/lib/EventsLib.sol rename to src/core/lib/EventsLib.sol diff --git a/src/lib/HooksLib.sol b/src/core/lib/HooksLib.sol similarity index 100% rename from src/lib/HooksLib.sol rename to src/core/lib/HooksLib.sol diff --git a/src/lib/StorageLib.sol b/src/core/lib/StorageLib.sol similarity index 99% rename from src/lib/StorageLib.sol rename to src/core/lib/StorageLib.sol index 7f2b8073..4577cc0d 100644 --- a/src/lib/StorageLib.sol +++ b/src/core/lib/StorageLib.sol @@ -22,8 +22,6 @@ struct AggregationVaultStorage { address withdrawalQueue; /// Mapping between strategy address and it's allocation config mapping(address => IEulerAggregationLayer.Strategy) strategies; - - /// lastInterestUpdate: last timestamo where interest was updated. uint40 lastInterestUpdate; /// interestSmearEnd: timestamp when the smearing of interest end. @@ -32,13 +30,10 @@ struct AggregationVaultStorage { uint168 interestLeft; /// locked: if locked or not for update. uint8 locked; - /// Address of balance tracker contract for reward streams integration. address balanceTracker; /// A mapping to check if a user address enabled balance forwarding for reward streams integration. mapping(address => bool) isBalanceForwarderEnabled; - - /// @dev storing the hooks target and kooked functions. uint256 hooksConfig; } diff --git a/src/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol similarity index 99% rename from src/module/AllocationPoints.sol rename to src/core/module/AllocationPoints.sol index b9106a77..24d34f62 100644 --- a/src/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -6,7 +6,7 @@ import {IERC4626} from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626 import {IWithdrawalQueue} from "../interface/IWithdrawalQueue.sol"; import {IEulerAggregationLayer} from "../interface/IEulerAggregationLayer.sol"; // contracts -import {Shared} from "../Shared.sol"; +import {Shared} from "../common/Shared.sol"; // libs import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; diff --git a/src/module/Fee.sol b/src/core/module/Fee.sol similarity index 97% rename from src/module/Fee.sol rename to src/core/module/Fee.sol index 1cec2773..8afa58e3 100644 --- a/src/module/Fee.sol +++ b/src/core/module/Fee.sol @@ -7,7 +7,7 @@ import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; // contracts -import {Shared} from "../Shared.sol"; +import {Shared} from "../common/Shared.sol"; // libs import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; diff --git a/src/module/Hooks.sol b/src/core/module/Hooks.sol similarity index 96% rename from src/module/Hooks.sol rename to src/core/module/Hooks.sol index 0c8c06a8..45d0f1a8 100644 --- a/src/module/Hooks.sol +++ b/src/core/module/Hooks.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; // interfaces import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; // contracts -import {Shared} from "../Shared.sol"; +import {Shared} from "../common/Shared.sol"; // libs import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {HooksLib} from "../lib/HooksLib.sol"; diff --git a/src/module/Rewards.sol b/src/core/module/Rewards.sol similarity index 99% rename from src/module/Rewards.sol rename to src/core/module/Rewards.sol index ae80ee88..c5bacc06 100644 --- a/src/module/Rewards.sol +++ b/src/core/module/Rewards.sol @@ -7,7 +7,7 @@ import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; // contracts -import {Shared} from "../Shared.sol"; +import {Shared} from "../common/Shared.sol"; // libs import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; diff --git a/src/plugin/Rebalancer.sol b/src/plugin/Rebalancer.sol index 14c663fc..57fea7d8 100644 --- a/src/plugin/Rebalancer.sol +++ b/src/plugin/Rebalancer.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; // interfaces -import {IEulerAggregationLayer} from "../interface/IEulerAggregationLayer.sol"; +import {IEulerAggregationLayer} from "../core/interface/IEulerAggregationLayer.sol"; import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; /// @title Rebalancer plugin diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index 5614403f..fa122a92 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.0; // interfaces import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; -import {IEulerAggregationLayer} from "../interface/IEulerAggregationLayer.sol"; -import {IWithdrawalQueue} from "../interface/IWithdrawalQueue.sol"; +import {IEulerAggregationLayer} from "../core/interface/IEulerAggregationLayer.sol"; +import {IWithdrawalQueue} from "../core/interface/IWithdrawalQueue.sol"; // contracts import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; diff --git a/test/common/EulerAggregationLayerBase.t.sol b/test/common/EulerAggregationLayerBase.t.sol index 15412d58..ca699396 100644 --- a/test/common/EulerAggregationLayerBase.t.sol +++ b/test/common/EulerAggregationLayerBase.t.sol @@ -3,19 +3,19 @@ pragma solidity ^0.8.0; // interfaces import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; -import {IWithdrawalQueue} from "../../src/interface/IWithdrawalQueue.sol"; +import {IWithdrawalQueue} from "../../src/core/interface/IWithdrawalQueue.sol"; // contracts import "evk/test/unit/evault/EVaultTestBase.t.sol"; -import {EulerAggregationLayer, IEulerAggregationLayer} from "../../src/EulerAggregationLayer.sol"; +import {EulerAggregationLayer, IEulerAggregationLayer} from "../../src/core/EulerAggregationLayer.sol"; import {Rebalancer} from "../../src/plugin/Rebalancer.sol"; -import {Hooks, HooksModule} from "../../src/module/Hooks.sol"; -import {Rewards} from "../../src/module/Rewards.sol"; -import {Fee} from "../../src/module/Fee.sol"; -import {EulerAggregationLayerFactory} from "../../src/EulerAggregationLayerFactory.sol"; +import {Hooks, HooksModule} from "../../src/core/module/Hooks.sol"; +import {Rewards} from "../../src/core/module/Rewards.sol"; +import {Fee} from "../../src/core/module/Fee.sol"; +import {EulerAggregationLayerFactory} from "../../src/core/EulerAggregationLayerFactory.sol"; import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; -import {AllocationPoints} from "../../src/module/AllocationPoints.sol"; +import {AllocationPoints} from "../../src/core/module/AllocationPoints.sol"; // libs -import {ErrorsLib} from "../../src/lib/ErrorsLib.sol"; +import {ErrorsLib} from "../../src/core/lib/ErrorsLib.sol"; contract EulerAggregationLayerBase is EVaultTestBase { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 218916d4..731c1c96 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -29,10 +29,9 @@ contract PerformanceFeeE2ETest is EulerAggregationLayerBase { } function testSetPerformanceFee() public { - { - (, uint256 fee) = eulerAggregationLayer.performanceFeeConfig(); - assertEq(fee, 0); - } + (address feeRecipientAddr, uint256 fee) = eulerAggregationLayer.performanceFeeConfig(); + assertEq(fee, 0); + assertEq(feeRecipientAddr, address(0)); uint256 newPerformanceFee = 3e17; @@ -41,7 +40,7 @@ contract PerformanceFeeE2ETest is EulerAggregationLayerBase { eulerAggregationLayer.setPerformanceFee(newPerformanceFee); vm.stopPrank(); - (address feeRecipientAddr, uint256 fee) = eulerAggregationLayer.performanceFeeConfig(); + (feeRecipientAddr, fee) = eulerAggregationLayer.performanceFeeConfig(); assertEq(fee, newPerformanceFee); assertEq(feeRecipientAddr, feeRecipient); } diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index a94638e6..d2d4a4c5 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -47,6 +47,8 @@ contract StrategyCapE2ETest is EulerAggregationLayerBase { } function testRebalanceAfterHittingCap() public { + address[] memory strategiesToRebalance = new address[](1); + uint256 cap = 3333333333333333333333; vm.prank(manager); eulerAggregationLayer.setStrategyCap(address(eTST), cap); @@ -82,7 +84,6 @@ contract StrategyCapE2ETest is EulerAggregationLayerBase { * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); vm.prank(user1); - address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); @@ -103,7 +104,6 @@ contract StrategyCapE2ETest is EulerAggregationLayerBase { uint256 strategyAllocatedBefore = (eulerAggregationLayer.getStrategy(address(eTST))).allocated; - address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); vm.stopPrank(); diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index 48972e8d..2a864a23 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -143,7 +143,6 @@ contract HarvestTest is EulerAggregationLayerBase { uint256 user1SocializedLoss = user1SharesBefore * negativeYield / eulerAggregationLayer.totalSupply(); uint256 expectedUser1Assets = user1SharesBefore * amountToDeposit / eulerAggregationLayer.totalSupply() - user1SocializedLoss; - uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); eulerAggregationLayer.harvest(); diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index 564d3358..4f7cb421 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -116,6 +116,7 @@ contract RebalanceTest is EulerAggregationLayerBase { } function testRebalanceByDepositingWhenToDepositIsGreaterThanCashAvailable() public { + address[] memory strategiesToRebalance = new address[](1); uint256 amountToDeposit = 10000e18; // deposit into aggregator @@ -139,7 +140,6 @@ contract RebalanceTest is EulerAggregationLayerBase { // rebalance into first strategy vm.warp(block.timestamp + 86400); vm.prank(user1); - address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); @@ -174,7 +174,6 @@ contract RebalanceTest is EulerAggregationLayerBase { uint256 expectedStrategyCash = currentCash - targetCash; vm.prank(user1); - address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTSTsecondary); rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); From e09ae6292ece8093c8fc9abe27002e0158161be3 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 27 Jun 2024 19:00:16 +0300 Subject: [PATCH 199/316] remove dependency to the EVC --- src/core/EulerAggregationLayer.sol | 8 +------- src/core/EulerAggregationLayerFactory.sol | 4 ---- src/core/common/Shared.sol | 11 ----------- src/core/interface/IEulerAggregationLayer.sol | 1 - src/core/lib/StorageLib.sol | 2 -- src/core/module/AllocationPoints.sol | 4 ++-- src/core/module/Rewards.sol | 7 +++---- test/common/EulerAggregationLayerBase.t.sol | 3 +-- test/e2e/BalanceForwarderE2ETest.t.sol | 1 - 9 files changed, 7 insertions(+), 34 deletions(-) diff --git a/src/core/EulerAggregationLayer.sol b/src/core/EulerAggregationLayer.sol index ed321133..fc108298 100644 --- a/src/core/EulerAggregationLayer.sol +++ b/src/core/EulerAggregationLayer.sol @@ -77,7 +77,6 @@ contract EulerAggregationLayer is cap: 0 }); $.totalAllocationPoints = _initParams.initialCashAllocationPoints; - $.evc = _initParams.evc; $.balanceTracker = _initParams.balanceTracker; // Setup DEFAULT_ADMIN @@ -437,7 +436,7 @@ contract EulerAggregationLayer is AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - if ($.totalAssetsDeposited == 0) return; + if (totalSupply() == 0) return; uint256 toGulp = totalAssetsAllocatable() - $.totalAssetsDeposited - $.interestLeft; if (toGulp == 0) return; @@ -592,11 +591,6 @@ contract EulerAggregationLayer is return $.interestLeft * timePassed / totalDuration; } - /// @dev Override for _msgSender() to be aware of the EVC forwarded calls. - function _msgSender() internal view override (ContextUpgradeable, Shared) returns (address) { - return Shared._msgSender(); - } - /// @dev Check if caller is WithdrawalQueue address, if not revert. function _isCallerWithdrawalQueue() internal view { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); diff --git a/src/core/EulerAggregationLayerFactory.sol b/src/core/EulerAggregationLayerFactory.sol index 3d0c06e2..83ea71b3 100644 --- a/src/core/EulerAggregationLayerFactory.sol +++ b/src/core/EulerAggregationLayerFactory.sol @@ -15,7 +15,6 @@ import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; /// @author Euler Labs (https://www.eulerlabs.com/) contract EulerAggregationLayerFactory { /// core dependencies - address public immutable evc; address public immutable balanceTracker; /// core modules implementations addresses address public immutable rewardsModuleImpl; @@ -30,7 +29,6 @@ contract EulerAggregationLayerFactory { /// @dev Init params struct. struct FactoryParams { - address evc; address balanceTracker; address rewardsModuleImpl; address hooksModuleImpl; @@ -43,7 +41,6 @@ contract EulerAggregationLayerFactory { /// @notice Constructor. /// @param _factoryParams FactoryParams struct. constructor(FactoryParams memory _factoryParams) { - evc = _factoryParams.evc; balanceTracker = _factoryParams.balanceTracker; rewardsModuleImpl = _factoryParams.rewardsModuleImpl; hooksModuleImpl = _factoryParams.hooksModuleImpl; @@ -83,7 +80,6 @@ contract EulerAggregationLayerFactory { new EulerAggregationLayer(rewardsModuleAddr, hooksModuleAddr, feeModuleAddr, allocationpointsModuleAddr); IEulerAggregationLayer.InitParams memory aggregationVaultInitParams = IEulerAggregationLayer.InitParams({ - evc: evc, balanceTracker: balanceTracker, withdrawalQueuePlugin: address(withdrawalQueue), rebalancerPlugin: rebalancer, diff --git a/src/core/common/Shared.sol b/src/core/common/Shared.sol index cd45771f..c503a595 100644 --- a/src/core/common/Shared.sol +++ b/src/core/common/Shared.sol @@ -38,17 +38,6 @@ contract Shared { $.locked = REENTRANCYLOCK__UNLOCKED; } - function _msgSender() internal view virtual returns (address) { - address sender = msg.sender; - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - if (sender == address($.evc)) { - (sender,) = IEVC($.evc).getCurrentOnBehalfOfAccount(address(0)); - } - - return sender; - } - function _setHooksConfig(address _hooksTarget, uint32 _hookedFns) internal { if (_hooksTarget != address(0) && IHookTarget(_hooksTarget).isHookTarget() != IHookTarget.isHookTarget.selector) { diff --git a/src/core/interface/IEulerAggregationLayer.sol b/src/core/interface/IEulerAggregationLayer.sol index cbabbb13..f516bee5 100644 --- a/src/core/interface/IEulerAggregationLayer.sol +++ b/src/core/interface/IEulerAggregationLayer.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.0; interface IEulerAggregationLayer { /// @dev Struct to pass init() call params. struct InitParams { - address evc; address balanceTracker; address withdrawalQueuePlugin; address rebalancerPlugin; diff --git a/src/core/lib/StorageLib.sol b/src/core/lib/StorageLib.sol index 4577cc0d..9bc480d8 100644 --- a/src/core/lib/StorageLib.sol +++ b/src/core/lib/StorageLib.sol @@ -6,8 +6,6 @@ import {IEulerAggregationLayer} from "../interface/IEulerAggregationLayer.sol"; /// @custom:storage-location erc7201:euler_aggregation_vault.storage.AggregationVault struct AggregationVaultStorage { - /// EVC address - address evc; /// Total amount of _asset deposited into EulerAggregationLayer contract uint256 totalAssetsDeposited; /// Total amount of _asset deposited across all strategies. diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index 24d34f62..80d5e286 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -69,7 +69,7 @@ abstract contract AllocationPointsModule is Shared { revert Errors.InvalidStrategyAsset(); } - _callHooksTarget(ADD_STRATEGY, _msgSender()); + _callHooksTarget(ADD_STRATEGY, msg.sender); $.strategies[_strategy] = IEulerAggregationLayer.Strategy({ allocated: 0, @@ -99,7 +99,7 @@ abstract contract AllocationPointsModule is Shared { revert Errors.AlreadyRemoved(); } - _callHooksTarget(REMOVE_STRATEGY, _msgSender()); + _callHooksTarget(REMOVE_STRATEGY, msg.sender); $.totalAllocationPoints -= strategyStorage.allocationPoints; strategyStorage.active = false; diff --git a/src/core/module/Rewards.sol b/src/core/module/Rewards.sol index c5bacc06..ca0ee81f 100644 --- a/src/core/module/Rewards.sol +++ b/src/core/module/Rewards.sol @@ -91,17 +91,16 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { /// @dev Only the authenticated account can enable balance forwarding for itself /// @dev Should call the IBalanceTracker hook with the current account's balance function enableBalanceForwarder() external virtual nonReentrant { - address user = _msgSender(); - uint256 userBalance = IERC20(address(this)).balanceOf(user); + uint256 userBalance = IERC20(address(this)).balanceOf(msg.sender); - _enableBalanceForwarder(user, userBalance); + _enableBalanceForwarder(msg.sender, userBalance); } /// @notice Disables balance forwarding for the authenticated account /// @dev Only the authenticated account can disable balance forwarding for itself /// @dev Should call the IBalanceTracker hook with the account's balance of 0 function disableBalanceForwarder() external virtual nonReentrant { - _disableBalanceForwarder(_msgSender()); + _disableBalanceForwarder(msg.sender); } /// @notice Retrieve the address of rewards contract, tracking changes in account's balances diff --git a/test/common/EulerAggregationLayerBase.t.sol b/test/common/EulerAggregationLayerBase.t.sol index ca699396..159a85ef 100644 --- a/test/common/EulerAggregationLayerBase.t.sol +++ b/test/common/EulerAggregationLayerBase.t.sol @@ -55,7 +55,6 @@ contract EulerAggregationLayerBase is EVaultTestBase { withdrawalQueueImpl = new WithdrawalQueue(); EulerAggregationLayerFactory.FactoryParams memory factoryParams = EulerAggregationLayerFactory.FactoryParams({ - evc: address(evc), balanceTracker: address(0), rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), @@ -91,7 +90,7 @@ contract EulerAggregationLayerBase is EVaultTestBase { vm.stopPrank(); } - function testInitialParams() public { + function testInitialParams() public view { EulerAggregationLayer.Strategy memory cashReserve = eulerAggregationLayer.getStrategy(address(0)); assertEq(cashReserve.allocated, 0); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 25d07165..58537e3d 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -26,7 +26,6 @@ contract BalanceForwarderE2ETest is EulerAggregationLayerBase { trackingReward = address(new TrackingRewardStreams(address(evc), 2 weeks)); EulerAggregationLayerFactory.FactoryParams memory factoryParams = EulerAggregationLayerFactory.FactoryParams({ - evc: address(evc), balanceTracker: trackingReward, rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), From 7a637569746d65e8be379b89f8de2d6b802e8ab8 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:57:31 +0300 Subject: [PATCH 200/316] feat: add setters for plugins --- .github/workflows/test.yml | 2 +- foundry.toml | 2 +- src/core/EulerAggregationLayer.sol | 108 ++++++++++++------ src/core/EulerAggregationLayerFactory.sol | 2 +- src/core/interface/IEulerAggregationLayer.sol | 2 +- src/core/lib/ErrorsLib.sol | 2 + src/core/lib/EventsLib.sol | 2 + src/core/lib/StorageLib.sol | 8 +- src/plugin/Rebalancer.sol | 1 - test/common/EulerAggregationLayerBase.t.sol | 13 +-- test/e2e/BalanceForwarderE2ETest.t.sol | 4 +- test/unit/RebalanceTest.t.sol | 10 +- test/unit/SetRebalancerTest.t.sol | 25 ++++ test/unit/SetWithdrawalQueueTest.t.sol | 25 ++++ 14 files changed, 155 insertions(+), 51 deletions(-) create mode 100644 test/unit/SetRebalancerTest.t.sol create mode 100644 test/unit/SetWithdrawalQueueTest.t.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 16f20b19..751c33a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: version: nightly - name: Run foundry build - run: forge build --sizes + run: forge build --force --sizes - name: Run foundry fmt check run: forge fmt --check diff --git a/foundry.toml b/foundry.toml index 32d5e99e..401821aa 100644 --- a/foundry.toml +++ b/foundry.toml @@ -18,7 +18,7 @@ quote_style = "double" number_underscore = "preserve" override_spacing = true ignore = [ - "src/lib/StorageLib.sol" + "src/core/lib/StorageLib.sol" ] [profile.test] diff --git a/src/core/EulerAggregationLayer.sol b/src/core/EulerAggregationLayer.sol index fc108298..2828e288 100644 --- a/src/core/EulerAggregationLayer.sol +++ b/src/core/EulerAggregationLayer.sol @@ -47,10 +47,8 @@ contract EulerAggregationLayer is bytes32 public constant STRATEGY_ADDER_ADMIN = keccak256("STRATEGY_ADDER_ADMIN"); bytes32 public constant STRATEGY_REMOVER = keccak256("STRATEGY_REMOVER"); bytes32 public constant STRATEGY_REMOVER_ADMIN = keccak256("STRATEGY_REMOVER_ADMIN"); - bytes32 public constant AGGREGATION_VAULT_MANAGER = keccak256("AGGREGATION_VAULT_MANAGER"); - bytes32 public constant AGGREGATION_VAULT_MANAGER_ADMIN = keccak256("AGGREGATION_VAULT_MANAGER_ADMIN"); - bytes32 public constant REBALANCER = keccak256("REBALANCER"); - bytes32 public constant REBALANCER_ADMIN = keccak256("REBALANCER_ADMIN"); + bytes32 public constant AGGREGATION_LAYER_MANAGER = keccak256("AGGREGATION_LAYER_MANAGER"); + bytes32 public constant AGGREGATION_LAYER_MANAGER_ADMIN = keccak256("AGGREGATION_LAYER_MANAGER_ADMIN"); /// @dev interest rate smearing period uint256 public constant INTEREST_SMEAR = 2 weeks; @@ -70,6 +68,7 @@ contract EulerAggregationLayer is AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); $.locked = REENTRANCYLOCK__UNLOCKED; $.withdrawalQueue = _initParams.withdrawalQueuePlugin; + $.rebalancer = _initParams.rebalancerPlugin; $.strategies[address(0)] = IEulerAggregationLayer.Strategy({ allocated: 0, allocationPoints: _initParams.initialCashAllocationPoints.toUint120(), @@ -80,30 +79,26 @@ contract EulerAggregationLayer is $.balanceTracker = _initParams.balanceTracker; // Setup DEFAULT_ADMIN - _grantRole(DEFAULT_ADMIN_ROLE, _initParams.aggregationVaultOwner); + _grantRole(DEFAULT_ADMIN_ROLE, _initParams.aggregationLayerOwner); // Setup role admins _setRoleAdmin(ALLOCATIONS_MANAGER, ALLOCATIONS_MANAGER_ADMIN); _setRoleAdmin(STRATEGY_ADDER, STRATEGY_ADDER_ADMIN); _setRoleAdmin(STRATEGY_REMOVER, STRATEGY_REMOVER_ADMIN); - _setRoleAdmin(AGGREGATION_VAULT_MANAGER, AGGREGATION_VAULT_MANAGER_ADMIN); - _setRoleAdmin(REBALANCER, REBALANCER_ADMIN); - - // By default, the Rebalancer contract is assigned the REBALANCER role - _grantRole(REBALANCER, _initParams.rebalancerPlugin); + _setRoleAdmin(AGGREGATION_LAYER_MANAGER, AGGREGATION_LAYER_MANAGER_ADMIN); } /// @dev See {FeeModule-setFeeRecipient}. - function setFeeRecipient(address _newFeeRecipient) external onlyRole(AGGREGATION_VAULT_MANAGER) use(MODULE_FEE) {} + function setFeeRecipient(address _newFeeRecipient) external onlyRole(AGGREGATION_LAYER_MANAGER) use(MODULE_FEE) {} /// @dev See {FeeModule-setPerformanceFee}. - function setPerformanceFee(uint256 _newFee) external onlyRole(AGGREGATION_VAULT_MANAGER) use(MODULE_FEE) {} + function setPerformanceFee(uint256 _newFee) external onlyRole(AGGREGATION_LAYER_MANAGER) use(MODULE_FEE) {} /// @dev See {RewardsModule-optInStrategyRewards}. function optInStrategyRewards(address _strategy) external override - onlyRole(AGGREGATION_VAULT_MANAGER) + onlyRole(AGGREGATION_LAYER_MANAGER) use(MODULE_REWARDS) {} @@ -111,7 +106,7 @@ contract EulerAggregationLayer is function optOutStrategyRewards(address _strategy) external override - onlyRole(AGGREGATION_VAULT_MANAGER) + onlyRole(AGGREGATION_LAYER_MANAGER) use(MODULE_REWARDS) {} @@ -119,7 +114,7 @@ contract EulerAggregationLayer is function enableRewardForStrategy(address _strategy, address _reward) external override - onlyRole(AGGREGATION_VAULT_MANAGER) + onlyRole(AGGREGATION_LAYER_MANAGER) use(MODULE_REWARDS) {} @@ -127,7 +122,7 @@ contract EulerAggregationLayer is function disableRewardForStrategy(address _strategy, address _reward, bool _forfeitRecentReward) external override - onlyRole(AGGREGATION_VAULT_MANAGER) + onlyRole(AGGREGATION_LAYER_MANAGER) use(MODULE_REWARDS) {} @@ -135,15 +130,17 @@ contract EulerAggregationLayer is function claimStrategyReward(address _strategy, address _reward, address _recipient, bool _forfeitRecentReward) external override - onlyRole(AGGREGATION_VAULT_MANAGER) + onlyRole(AGGREGATION_LAYER_MANAGER) use(MODULE_REWARDS) {} - /// @dev See {RewardsModule-enableBalanceForwarder}. - function enableBalanceForwarder() external override use(MODULE_REWARDS) {} - - /// @dev See {RewardsModule-disableBalanceForwarder}. - function disableBalanceForwarder() external override use(MODULE_REWARDS) {} + /// @dev See {HooksModule-setHooksConfig}. + function setHooksConfig(address _hooksTarget, uint32 _hookedFns) + external + override + onlyRole(AGGREGATION_LAYER_MANAGER) + use(MODULE_HOOKS) + {} /// @dev See {AllocationPointsModule-adjustAllocationPoints}. function adjustAllocationPoints(address _strategy, uint256 _newPoints) @@ -169,26 +166,48 @@ contract EulerAggregationLayer is /// @dev See {AllocationPointsModule-removeStrategy}. function removeStrategy(address _strategy) external use(MODULE_ALLOCATION_POINTS) onlyRole(STRATEGY_REMOVER) {} - /// @dev See {HooksModule-setHooksConfig}. - function setHooksConfig(address _hooksTarget, uint32 _hookedFns) - external - override - onlyRole(AGGREGATION_VAULT_MANAGER) - use(MODULE_HOOKS) - {} + /// @dev See {RewardsModule-enableBalanceForwarder}. + function enableBalanceForwarder() external override use(MODULE_REWARDS) {} + + /// @dev See {RewardsModule-disableBalanceForwarder}. + function disableBalanceForwarder() external override use(MODULE_REWARDS) {} + + /// @notice Set a new address for WithdrawalQueue plugin. + /// @dev Can only be called by an address with the `AGGREGATION_LAYER_MANAGER` role. + /// @param _withdrawalQueue New WithdrawalQueue contract address. + function setWithdrawalQueue(address _withdrawalQueue) external onlyRole(AGGREGATION_LAYER_MANAGER) { + if (_withdrawalQueue == address(0)) revert Errors.InvalidPlugin(); + + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + emit Events.SetWithdrawalQueue($.withdrawalQueue, _withdrawalQueue); + + $.withdrawalQueue = _withdrawalQueue; + } + + /// @notice Set a new address for Rebalancer plugin. + /// @dev Can only be called by an address with the `AGGREGATION_LAYER_MANAGER` role. + /// @param _rebalancer New Rebalancer contract address. + function setRebalancer(address _rebalancer) external onlyRole(AGGREGATION_LAYER_MANAGER) { + if (_rebalancer == address(0)) revert Errors.InvalidPlugin(); + + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + emit Events.SetRebalancer($.rebalancer, _rebalancer); + + $.rebalancer = _rebalancer; + } /// @notice Rebalance strategy by depositing or withdrawing the amount to rebalance to hit target allocation. - /// @dev Can only be called by an address that hold the REBALANCER role. + /// @dev Can only be called only by the WithdrawalQueue plugin. /// @param _strategy Strategy address. /// @param _amountToRebalance Amount to deposit or withdraw. /// @param _isDeposit bool to indicate if it is a deposit or a withdraw. - function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) - external - nonReentrant - onlyRole(REBALANCER) - { + function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + if (_msgSender() != $.rebalancer) revert Errors.NotRebalancer(); + IEulerAggregationLayer.Strategy memory strategyData = $.strategies[_strategy]; if (_isDeposit) { @@ -295,30 +314,49 @@ contract EulerAggregationLayer is return avsr; } + /// @notice Get the total allocated amount. + /// @return uint256 Total allocated. function totalAllocated() external view returns (uint256) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.totalAllocated; } + /// @notice Get the total allocation points. + /// @return uint256 Total allocation points. function totalAllocationPoints() external view returns (uint256) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.totalAllocationPoints; } + /// @notice Get the total assets deposited into the aggregation layer. + /// @return uint256 Total assets deposited. function totalAssetsDeposited() external view returns (uint256) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.totalAssetsDeposited; } + /// @notice Get the WithdrawalQueue plugin address. + /// @return address Withdrawal queue address. function withdrawalQueue() external view returns (address) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.withdrawalQueue; } + /// @notice Get the Rebalancer plugin address. + /// @return address Rebalancer address. + function rebalancer() external view returns (address) { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + return $.rebalancer; + } + + /// @notice Get the performance fee config. + /// @return adddress Fee recipient. + /// @return uint256 Fee percentage. function performanceFeeConfig() external view returns (address, uint256) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); diff --git a/src/core/EulerAggregationLayerFactory.sol b/src/core/EulerAggregationLayerFactory.sol index 83ea71b3..b32b454c 100644 --- a/src/core/EulerAggregationLayerFactory.sol +++ b/src/core/EulerAggregationLayerFactory.sol @@ -83,7 +83,7 @@ contract EulerAggregationLayerFactory { balanceTracker: balanceTracker, withdrawalQueuePlugin: address(withdrawalQueue), rebalancerPlugin: rebalancer, - aggregationVaultOwner: msg.sender, + aggregationLayerOwner: msg.sender, asset: _asset, name: _name, symbol: _symbol, diff --git a/src/core/interface/IEulerAggregationLayer.sol b/src/core/interface/IEulerAggregationLayer.sol index f516bee5..9d0d2287 100644 --- a/src/core/interface/IEulerAggregationLayer.sol +++ b/src/core/interface/IEulerAggregationLayer.sol @@ -7,7 +7,7 @@ interface IEulerAggregationLayer { address balanceTracker; address withdrawalQueuePlugin; address rebalancerPlugin; - address aggregationVaultOwner; + address aggregationLayerOwner; address asset; string name; string symbol; diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index b7ed440b..2f08f156 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -24,4 +24,6 @@ library ErrorsLib { error InvalidHookedFns(); error EmptyError(); error NotWithdrawaQueue(); + error InvalidPlugin(); + error NotRebalancer(); } diff --git a/src/core/lib/EventsLib.sol b/src/core/lib/EventsLib.sol index 024b6915..7c4df926 100644 --- a/src/core/lib/EventsLib.sol +++ b/src/core/lib/EventsLib.sol @@ -8,6 +8,8 @@ library EventsLib { event Rebalance(address indexed strategy, uint256 amountToRebalance, bool isDeposit); event ExecuteHarvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); event Harvest(uint256 totalAllocated, uint256 totlaYield, uint256 totalLoss); + event SetWithdrawalQueue(address _oldWithdrawalQueuePlugin, address _newWithdrawalQueuePlugin); + event SetRebalancer(address _oldRebalancer, address _newRebalancer); /// @dev Allocationpoints events event AdjustAllocationPoints(address indexed strategy, uint256 oldPoints, uint256 newPoints); diff --git a/src/core/lib/StorageLib.sol b/src/core/lib/StorageLib.sol index 9bc480d8..21fcd2e2 100644 --- a/src/core/lib/StorageLib.sol +++ b/src/core/lib/StorageLib.sol @@ -16,8 +16,10 @@ struct AggregationVaultStorage { uint256 performanceFee; /// fee recipient address address feeRecipient; - /// Withdrawal queue contract's address + /// WithdrawalQueue plugin address address withdrawalQueue; + /// Rebalancer plugin address + address rebalancer; /// Mapping between strategy address and it's allocation config mapping(address => IEulerAggregationLayer.Strategy) strategies; /// lastInterestUpdate: last timestamo where interest was updated. @@ -28,10 +30,14 @@ struct AggregationVaultStorage { uint168 interestLeft; /// locked: if locked or not for update. uint8 locked; + + /// Address of balance tracker contract for reward streams integration. address balanceTracker; /// A mapping to check if a user address enabled balance forwarding for reward streams integration. mapping(address => bool) isBalanceForwarderEnabled; + + /// @dev storing the hooks target and kooked functions. uint256 hooksConfig; } diff --git a/src/plugin/Rebalancer.sol b/src/plugin/Rebalancer.sol index 57fea7d8..cef89eec 100644 --- a/src/plugin/Rebalancer.sol +++ b/src/plugin/Rebalancer.sol @@ -9,7 +9,6 @@ import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.s /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) /// @notice A contract to execute rebalance() on the EulerAggregationLayer. -/// @dev Usually this contract will hold the `REBALANCER` role. contract Rebalancer { event ExecuteRebalance( address indexed curatedVault, diff --git a/test/common/EulerAggregationLayerBase.t.sol b/test/common/EulerAggregationLayerBase.t.sol index 159a85ef..5c43c086 100644 --- a/test/common/EulerAggregationLayerBase.t.sol +++ b/test/common/EulerAggregationLayerBase.t.sol @@ -76,15 +76,14 @@ contract EulerAggregationLayerBase is EVaultTestBase { eulerAggregationLayer.grantRole(eulerAggregationLayer.ALLOCATIONS_MANAGER_ADMIN(), deployer); eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_ADDER_ADMIN(), deployer); eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_REMOVER_ADMIN(), deployer); - eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); - eulerAggregationLayer.grantRole(eulerAggregationLayer.REBALANCER_ADMIN(), deployer); + eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER_ADMIN(), deployer); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager eulerAggregationLayer.grantRole(eulerAggregationLayer.ALLOCATIONS_MANAGER(), manager); eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_ADDER(), manager); eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_REMOVER(), manager); - eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER(), manager); + eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER(), manager); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); vm.stopPrank(); @@ -110,8 +109,8 @@ contract EulerAggregationLayerBase is EVaultTestBase { eulerAggregationLayer.STRATEGY_REMOVER_ADMIN() ); assertEq( - eulerAggregationLayer.getRoleAdmin(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER()), - eulerAggregationLayer.AGGREGATION_VAULT_MANAGER_ADMIN() + eulerAggregationLayer.getRoleAdmin(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER()), + eulerAggregationLayer.AGGREGATION_LAYER_MANAGER_ADMIN() ); assertEq( withdrawalQueue.getRoleAdmin(withdrawalQueue.WITHDRAW_QUEUE_MANAGER()), @@ -121,13 +120,13 @@ contract EulerAggregationLayerBase is EVaultTestBase { assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.ALLOCATIONS_MANAGER_ADMIN(), deployer)); assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.STRATEGY_ADDER_ADMIN(), deployer)); assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.STRATEGY_REMOVER_ADMIN(), deployer)); - assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer)); + assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER_ADMIN(), deployer)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.ALLOCATIONS_MANAGER(), manager)); assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.STRATEGY_ADDER(), manager)); assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.STRATEGY_REMOVER(), manager)); - assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER(), manager)); + assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER(), manager)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); } diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 58537e3d..63bf1521 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -46,14 +46,14 @@ contract BalanceForwarderE2ETest is EulerAggregationLayerBase { // eulerAggregationLayer.grantRole(eulerAggregationLayer.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_ADDER_ADMIN(), deployer); eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_REMOVER_ADMIN(), deployer); - eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); + eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER_ADMIN(), deployer); // grant roles to manager eulerAggregationLayer.grantRole(eulerAggregationLayer.ALLOCATIONS_MANAGER(), manager); // eulerAggregationLayer.grantRole(eulerAggregationLayer.WITHDRAW_QUEUE_MANAGER(), manager); eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_ADDER(), manager); eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_REMOVER(), manager); - eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_VAULT_MANAGER(), manager); + eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index 4f7cb421..1206633e 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -9,7 +9,8 @@ import { IEVault, IRMTestDefault, TestERC20, - IEulerAggregationLayer + IEulerAggregationLayer, + ErrorsLib } from "../common/EulerAggregationLayerBase.t.sol"; contract RebalanceTest is EulerAggregationLayerBase { @@ -346,4 +347,11 @@ contract RebalanceTest is EulerAggregationLayerBase { // ); // assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated - eTSTMaxWithdraw); } + + function testRebalanceFromRandomSender() public { + vm.startPrank(user1); + vm.expectRevert(ErrorsLib.NotRebalancer.selector); + eulerAggregationLayer.rebalance(address(eTST), 1, true); + vm.stopPrank(); + } } diff --git a/test/unit/SetRebalancerTest.t.sol b/test/unit/SetRebalancerTest.t.sol new file mode 100644 index 00000000..6073582d --- /dev/null +++ b/test/unit/SetRebalancerTest.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {EulerAggregationLayerBase, EulerAggregationLayer, ErrorsLib} from "../common/EulerAggregationLayerBase.t.sol"; + +contract SetRebalancerTest is EulerAggregationLayerBase { + function setUp() public virtual override { + super.setUp(); + } + + function testSetInvalidRebalancer() public { + vm.startPrank(manager); + vm.expectRevert(ErrorsLib.InvalidPlugin.selector); + eulerAggregationLayer.setRebalancer(address(0)); + } + + function testSetRebalancer() public { + address newRebalancer = makeAddr("Rebalancer"); + + vm.prank(manager); + eulerAggregationLayer.setRebalancer(newRebalancer); + + assertEq(eulerAggregationLayer.rebalancer(), newRebalancer); + } +} diff --git a/test/unit/SetWithdrawalQueueTest.t.sol b/test/unit/SetWithdrawalQueueTest.t.sol new file mode 100644 index 00000000..f0011142 --- /dev/null +++ b/test/unit/SetWithdrawalQueueTest.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {EulerAggregationLayerBase, EulerAggregationLayer, ErrorsLib} from "../common/EulerAggregationLayerBase.t.sol"; + +contract SetWithdrawalQueueTest is EulerAggregationLayerBase { + function setUp() public virtual override { + super.setUp(); + } + + function testSetInvalidWithdrawalQueue() public { + vm.startPrank(manager); + vm.expectRevert(ErrorsLib.InvalidPlugin.selector); + eulerAggregationLayer.setWithdrawalQueue(address(0)); + } + + function testSetWithdrawalQueue() public { + address newWithdrawalQueue = makeAddr("WITHDRAWAL_QUEUE"); + + vm.prank(manager); + eulerAggregationLayer.setWithdrawalQueue(newWithdrawalQueue); + + assertEq(eulerAggregationLayer.withdrawalQueue(), newWithdrawalQueue); + } +} From 624b79efaf0259b30656d8d2b02c37d04628b733 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:16:57 +0300 Subject: [PATCH 201/316] forge install: erc4626-tests --- .gitmodules | 3 +++ lib/erc4626-tests | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/erc4626-tests diff --git a/.gitmodules b/.gitmodules index 02ccc320..68a9c6bf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,3 +17,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "lib/erc4626-tests"] + path = lib/erc4626-tests + url = https://github.com/a16z/erc4626-tests diff --git a/lib/erc4626-tests b/lib/erc4626-tests new file mode 160000 index 00000000..8b1d7c2a --- /dev/null +++ b/lib/erc4626-tests @@ -0,0 +1 @@ +Subproject commit 8b1d7c2ac248c33c3506b1bff8321758943c5e11 From 5fbd9d8a4a0ebf240743ab79342322eca711627a Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:49:38 +0300 Subject: [PATCH 202/316] test: a16z erc4626 property tests --- test/A16zPropertyTests.t.sol | 62 ++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 test/A16zPropertyTests.t.sol diff --git a/test/A16zPropertyTests.t.sol b/test/A16zPropertyTests.t.sol new file mode 100644 index 00000000..3c35e0c9 --- /dev/null +++ b/test/A16zPropertyTests.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +// a16z property tests +import {ERC4626Test} from "erc4626-tests/ERC4626.test.sol"; +// contracts +import {EulerAggregationLayer} from "../src/core/EulerAggregationLayer.sol"; +import {Rebalancer} from "../src/plugin/Rebalancer.sol"; +import {Hooks} from "../src/core/module/Hooks.sol"; +import {Rewards} from "../src/core/module/Rewards.sol"; +import {Fee} from "../src/core/module/Fee.sol"; +import {EulerAggregationLayerFactory} from "../src/core/EulerAggregationLayerFactory.sol"; +import {WithdrawalQueue} from "../src/plugin/WithdrawalQueue.sol"; +import {AllocationPoints} from "../src/core/module/AllocationPoints.sol"; +// mocks +import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol"; + +contract A16zPropertyTests is ERC4626Test { + uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; + + // core modules + Rewards rewardsImpl; + Hooks hooksImpl; + Fee feeModuleImpl; + AllocationPoints allocationPointsModuleImpl; + // plugins + Rebalancer rebalancerPlugin; + WithdrawalQueue withdrawalQueuePluginImpl; + + EulerAggregationLayerFactory eulerAggregationLayerFactory; + EulerAggregationLayer eulerAggregationLayer; + WithdrawalQueue withdrawalQueue; + + function setUp() public override { + rewardsImpl = new Rewards(); + hooksImpl = new Hooks(); + feeModuleImpl = new Fee(); + allocationPointsModuleImpl = new AllocationPoints(); + + rebalancerPlugin = new Rebalancer(); + withdrawalQueuePluginImpl = new WithdrawalQueue(); + + EulerAggregationLayerFactory.FactoryParams memory factoryParams = EulerAggregationLayerFactory.FactoryParams({ + balanceTracker: address(0), + rewardsModuleImpl: address(rewardsImpl), + hooksModuleImpl: address(hooksImpl), + feeModuleImpl: address(feeModuleImpl), + allocationPointsModuleImpl: address(allocationPointsModuleImpl), + rebalancer: address(rebalancerPlugin), + withdrawalQueueImpl: address(withdrawalQueuePluginImpl) + }); + eulerAggregationLayerFactory = new EulerAggregationLayerFactory(factoryParams); + + _underlying_ = address(new ERC20Mock()); + _vault_ = eulerAggregationLayerFactory.deployEulerAggregationLayer( + _underlying_, "E20M_Agg", "E20M_Agg", CASH_RESERVE_ALLOCATION_POINTS + ); + _delta_ = 0; + _vaultMayBeEmpty = false; + _unlimitedAmount = false; + } +} From bfbe585bc9186c5c07739080aad47c87eb7a3062 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:53:36 +0300 Subject: [PATCH 203/316] clean --- test/A16zPropertyTests.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/A16zPropertyTests.t.sol b/test/A16zPropertyTests.t.sol index 3c35e0c9..27c0f070 100644 --- a/test/A16zPropertyTests.t.sol +++ b/test/A16zPropertyTests.t.sol @@ -29,7 +29,6 @@ contract A16zPropertyTests is ERC4626Test { EulerAggregationLayerFactory eulerAggregationLayerFactory; EulerAggregationLayer eulerAggregationLayer; - WithdrawalQueue withdrawalQueue; function setUp() public override { rewardsImpl = new Rewards(); From f8269f8a8ff6b2066749d85960517762466214e6 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:20:16 +0300 Subject: [PATCH 204/316] init basic invariants setup --- foundry.toml | 1 + .../EulerAggregationLayerInvariants.t.sol | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 test/invariant/EulerAggregationLayerInvariants.t.sol diff --git a/foundry.toml b/foundry.toml index 401821aa..82ad47cc 100644 --- a/foundry.toml +++ b/foundry.toml @@ -56,6 +56,7 @@ call_override = false dictionary_weight = 80 include_storage = true include_push_bytes = true +match_test = "invariant_" [profile.coverage] via_ir = true diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol new file mode 100644 index 00000000..564cdb30 --- /dev/null +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + EulerAggregationLayerBase, + EulerAggregationLayer, + IWithdrawalQueue +} from "../common/EulerAggregationLayerBase.t.sol"; + +contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { + function setUp() public override { + super.setUp(); + + targetContract(address(eulerAggregationLayer)); + } + + function invariant_totalAllocationPoints() public view { + address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); + + (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = + IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); + + uint256 expectedTotalAllocationpoints; + expectedTotalAllocationpoints += (eulerAggregationLayer.getStrategy(address(0))).allocationPoints; + for (uint256 i; i < withdrawalQueueLength; i++) { + expectedTotalAllocationpoints += + (eulerAggregationLayer.getStrategy(withdrawalQueueArray[i])).allocationPoints; + } + + assertEq(eulerAggregationLayer.totalAllocationPoints(), expectedTotalAllocationpoints); + } +} From c28a76c2cb00373867fc5e1c2e73fc98268663d6 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:17:10 +0300 Subject: [PATCH 205/316] improve setup; more invariants; fixes --- foundry.toml | 2 +- src/core/interface/IEulerAggregationLayer.sol | 3 + src/core/lib/ErrorsLib.sol | 1 + src/core/module/AllocationPoints.sol | 3 + test/A16zPropertyTests.t.sol | 4 + test/common/EulerAggregationLayerBase.t.sol | 9 ++ test/e2e/HooksE2ETest.t.sol | 10 ++ .../EulerAggregationLayerInvariants.t.sol | 50 +++++- .../handler/EulerAggregationLayerHandler.sol | 153 ++++++++++++++++++ test/invariant/util/Actor.sol | 52 ++++++ test/invariant/util/Strategy.sol | 20 +++ 11 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 test/invariant/handler/EulerAggregationLayerHandler.sol create mode 100644 test/invariant/util/Actor.sol create mode 100644 test/invariant/util/Strategy.sol diff --git a/foundry.toml b/foundry.toml index 82ad47cc..b4528b40 100644 --- a/foundry.toml +++ b/foundry.toml @@ -50,7 +50,7 @@ match_contract = "Fuzz" [profile.invariant] runs = 256 -depth = 15 +depth = 500 fail_on_revert = false call_override = false dictionary_weight = 80 diff --git a/src/core/interface/IEulerAggregationLayer.sol b/src/core/interface/IEulerAggregationLayer.sol index 9d0d2287..c0a165e8 100644 --- a/src/core/interface/IEulerAggregationLayer.sol +++ b/src/core/interface/IEulerAggregationLayer.sol @@ -50,6 +50,9 @@ interface IEulerAggregationLayer { uint256 assets, uint256 shares ) external; + function adjustAllocationPoints(address _strategy, uint256 _newPoints) external; + function addStrategy(address _strategy, uint256 _allocationPoints) external; + function removeStrategy(address _strategy) external; function getStrategy(address _strategy) external view returns (Strategy memory); function totalAllocationPoints() external view returns (uint256); function totalAllocated() external view returns (uint256); diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index 2f08f156..fe7baa39 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -26,4 +26,5 @@ library ErrorsLib { error NotWithdrawaQueue(); error InvalidPlugin(); error NotRebalancer(); + error InvalidAllocationPoints(); } diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index 80d5e286..d70703f1 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -26,6 +26,7 @@ abstract contract AllocationPointsModule is Shared { function adjustAllocationPoints(address _strategy, uint256 _newPoints) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + if (_newPoints == 0) revert Errors.InvalidAllocationPoints(); IEulerAggregationLayer.Strategy memory strategyDataCache = $.strategies[_strategy]; if (!strategyDataCache.active) { @@ -69,6 +70,8 @@ abstract contract AllocationPointsModule is Shared { revert Errors.InvalidStrategyAsset(); } + if (_allocationPoints == 0) revert Errors.InvalidAllocationPoints(); + _callHooksTarget(ADD_STRATEGY, msg.sender); $.strategies[_strategy] = IEulerAggregationLayer.Strategy({ diff --git a/test/A16zPropertyTests.t.sol b/test/A16zPropertyTests.t.sol index 27c0f070..4e33853b 100644 --- a/test/A16zPropertyTests.t.sol +++ b/test/A16zPropertyTests.t.sol @@ -58,4 +58,8 @@ contract A16zPropertyTests is ERC4626Test { _vaultMayBeEmpty = false; _unlimitedAmount = false; } + + function testToAvoidCoverage() public pure { + return; + } } diff --git a/test/common/EulerAggregationLayerBase.t.sol b/test/common/EulerAggregationLayerBase.t.sol index 5c43c086..22245e06 100644 --- a/test/common/EulerAggregationLayerBase.t.sol +++ b/test/common/EulerAggregationLayerBase.t.sol @@ -44,6 +44,7 @@ contract EulerAggregationLayerBase is EVaultTestBase { deployer = makeAddr("Deployer"); user1 = makeAddr("User_1"); user2 = makeAddr("User_2"); + manager = makeAddr("Manager"); vm.startPrank(deployer); rewardsImpl = new Rewards(); @@ -87,6 +88,14 @@ contract EulerAggregationLayerBase is EVaultTestBase { withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); vm.stopPrank(); + + vm.label(address(eulerAggregationLayerFactory), "eulerAggregationLayerFactory"); + vm.label(address(eulerAggregationLayer), "eulerAggregationLayer"); + vm.label(eulerAggregationLayer.MODULE_REWARDS(), "MODULE_REWARDS"); + vm.label(eulerAggregationLayer.MODULE_HOOKS(), "MODULE_HOOKS"); + vm.label(eulerAggregationLayer.MODULE_FEE(), "MODULE_FEE"); + vm.label(eulerAggregationLayer.MODULE_ALLOCATION_POINTS(), "MODULE_ALLOCATION_POINTS"); + vm.label(address(assetTST), "assetTST"); } function testInitialParams() public view { diff --git a/test/e2e/HooksE2ETest.t.sol b/test/e2e/HooksE2ETest.t.sol index 04a87aee..37709f27 100644 --- a/test/e2e/HooksE2ETest.t.sol +++ b/test/e2e/HooksE2ETest.t.sol @@ -104,10 +104,20 @@ contract HooksContract is IHookTarget { } fallback() external payable {} + + receive() external payable {} + + function testToAvoidCoverage() public pure { + return; + } } contract NotHooksContract is IHookTarget { function isHookTarget() external pure returns (bytes4) { return 0x0; } + + function testToAvoidCoverage() public pure { + return; + } } diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 564cdb30..24926966 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -4,14 +4,33 @@ pragma solidity ^0.8.0; import { EulerAggregationLayerBase, EulerAggregationLayer, - IWithdrawalQueue + IWithdrawalQueue, + console2 } from "../common/EulerAggregationLayerBase.t.sol"; +import {Actor} from "./util/Actor.sol"; +import {Strategy} from "./util/Strategy.sol"; +import {EulerAggregationLayerHandler} from "./handler/EulerAggregationLayerHandler.sol"; contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { + Actor internal actorUtil; + Strategy internal strategyUtil; + + EulerAggregationLayerHandler internal eulerAggregationLayerHandler; + function setUp() public override { super.setUp(); - targetContract(address(eulerAggregationLayer)); + actorUtil = new Actor(); + actorUtil.addActor(manager); + actorUtil.addActor(deployer); + actorUtil.addActor(user1); + actorUtil.addActor(user2); + + strategyUtil = new Strategy(); + strategyUtil.includeStrategy(address(eTST)); + + eulerAggregationLayerHandler = new EulerAggregationLayerHandler(eulerAggregationLayer, actorUtil, strategyUtil); + targetContract(address(eulerAggregationLayerHandler)); } function invariant_totalAllocationPoints() public view { @@ -29,4 +48,31 @@ contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { assertEq(eulerAggregationLayer.totalAllocationPoints(), expectedTotalAllocationpoints); } + + function invariant_withdrawalQueue() public view { + address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); + + (, uint256 withdrawalQueueLength) = IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); + + uint256 cashReserveAllocationPoints = (eulerAggregationLayer.getStrategy(address(0))).allocationPoints; + + if (eulerAggregationLayer.totalAllocationPoints() - cashReserveAllocationPoints == 0) { + assertEq(withdrawalQueueLength, 0); + } else { + assertGt(withdrawalQueueLength, 0); + } + } + + // function invariant_totalAssetsDeposited() public view { + // address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); + + // (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = + // IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); + + // uint256 aggregatedAllocatedAmount; + // for (uint256 i; i < withdrawalQueueLength; i++) { + // aggregatedAllocatedAmount += + // (eulerAggregationLayer.getStrategy(withdrawalQueueArray[i])).allocated; + // } + // } } diff --git a/test/invariant/handler/EulerAggregationLayerHandler.sol b/test/invariant/handler/EulerAggregationLayerHandler.sol new file mode 100644 index 00000000..c2ae2afc --- /dev/null +++ b/test/invariant/handler/EulerAggregationLayerHandler.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + Test, + EulerAggregationLayerBase, + EulerAggregationLayer, + console2, + EVault, + IEVault, + IRMTestDefault, + TestERC20, + IEulerAggregationLayer, + ErrorsLib, + IERC4626 +} from "../../common/EulerAggregationLayerBase.t.sol"; +import {Actor} from "../util/Actor.sol"; +import {Strategy} from "../util/Strategy.sol"; + +contract EulerAggregationLayerHandler is Test { + Actor internal actorUtil; + Strategy internal strategyUtil; + EulerAggregationLayer internal eulerAggLayer; + + // ghost vars + uint256 ghost_totalAllocationPoints; + uint256 ghost_totalAssetsDeposited; + mapping(address => uint256) ghost_allocationPoints; + + // last function call state + address currentActor; + uint256 currentActorIndex; + bool success; + bytes returnData; + + constructor(EulerAggregationLayer _eulerAggLayer, Actor _actor, Strategy _strategy) { + eulerAggLayer = _eulerAggLayer; + actorUtil = _actor; + strategyUtil = _strategy; + + // initiating ghost total allocation points to match count cash reserve. + ghost_totalAllocationPoints += eulerAggLayer.totalAllocationPoints(); + ghost_allocationPoints[address(0)] = ghost_totalAllocationPoints; + } + + function adjustAllocationPoints(uint256 _strategyIndexSeed, uint256 _newPoints) external { + address strategyAddr = strategyUtil.fetchStrategy(_strategyIndexSeed); + + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggLayer.getStrategy(strategyAddr); + + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( + 0, + address(eulerAggLayer), + abi.encodeWithSelector(IEulerAggregationLayer.adjustAllocationPoints.selector, strategyAddr, _newPoints) + ); + + if (success) { + ghost_totalAllocationPoints = ghost_totalAllocationPoints + _newPoints - strategyBefore.allocationPoints; + ghost_allocationPoints[strategyAddr] = _newPoints; + } + IEulerAggregationLayer.Strategy memory strategyAfter = eulerAggLayer.getStrategy(strategyAddr); + assertEq(eulerAggLayer.totalAllocationPoints(), ghost_totalAllocationPoints); + assertEq(strategyAfter.allocationPoints, ghost_allocationPoints[strategyAddr]); + } + + function addStrategy(uint256 _strategyIndexSeed, uint256 _allocationPoints) external { + address strategyAddr = strategyUtil.fetchStrategy(_strategyIndexSeed); + + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( + 0, + address(eulerAggLayer), + abi.encodeWithSelector(IEulerAggregationLayer.addStrategy.selector, strategyAddr, _allocationPoints) + ); + + if (success) { + ghost_totalAllocationPoints += _allocationPoints; + ghost_allocationPoints[strategyAddr] = _allocationPoints; + } + IEulerAggregationLayer.Strategy memory strategyAfter = eulerAggLayer.getStrategy(strategyAddr); + assertEq(eulerAggLayer.totalAllocationPoints(), ghost_totalAllocationPoints); + assertEq(strategyAfter.allocationPoints, ghost_allocationPoints[strategyAddr]); + } + + function removeStrategy(uint256 _strategyIndexSeed) external { + address strategyAddr = strategyUtil.fetchStrategy(_strategyIndexSeed); + + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggLayer.getStrategy(strategyAddr); + + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( + 0, + address(eulerAggLayer), + abi.encodeWithSelector(IEulerAggregationLayer.removeStrategy.selector, strategyAddr) + ); + + if (success) { + ghost_totalAllocationPoints -= strategyBefore.allocationPoints; + ghost_allocationPoints[strategyAddr] = 0; + } + IEulerAggregationLayer.Strategy memory strategyAfter = eulerAggLayer.getStrategy(strategyAddr); + assertEq(eulerAggLayer.totalAllocationPoints(), ghost_totalAllocationPoints); + assertEq(strategyAfter.allocationPoints, 0); + } + + function deposit(uint256 _actorIndexSeed, uint256 _assets, address _receiver) public { + vm.assume(_receiver != address(0)); + + (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); + + _fillBalance(currentActor, eulerAggLayer.asset(), _assets); + + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( + currentActorIndex, + address(eulerAggLayer), + abi.encodeWithSelector(IERC4626.deposit.selector, _assets, _receiver) + ); + + if (success) { + ghost_totalAssetsDeposited += _assets; + } + assertEq(eulerAggLayer.totalAssetsDeposited(), ghost_totalAssetsDeposited); + } + + function mint(uint256 _actorIndexSeed, uint256 _shares, address _receiver) public { + vm.assume(_receiver != address(0)); + + (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); + + uint256 assets = eulerAggLayer.previewMint(_shares); + _fillBalance(currentActor, eulerAggLayer.asset(), assets); + + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( + currentActorIndex, + address(eulerAggLayer), + abi.encodeWithSelector(IERC4626.mint.selector, _shares, _receiver) + ); + + if (success) { + ghost_totalAssetsDeposited += assets; + } + assertEq(eulerAggLayer.totalAssetsDeposited(), ghost_totalAssetsDeposited); + } + + function _fillBalance(address _actor, address _asset, uint256 _amount) internal { + TestERC20 asset = TestERC20(_asset); + + uint256 actorCurrentBalance = asset.balanceOf(currentActor); + if (actorCurrentBalance < _amount) { + asset.mint(currentActor, _amount - actorCurrentBalance); + } + vm.prank(_actor); + asset.approve(address(eulerAggLayer), _amount); + } +} diff --git a/test/invariant/util/Actor.sol b/test/invariant/util/Actor.sol new file mode 100644 index 00000000..d00f1791 --- /dev/null +++ b/test/invariant/util/Actor.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Test} from "forge-std/Test.sol"; + +contract Actor is Test { + /// @dev actor[0] will always be a manager address that have access to all EulerAggregationLayer roles. + address[] public actors; + + function addActor(address _actor) external { + actors.push(_actor); + } + + function initiateExactActorCall(uint256 _actorIndex, address _target, bytes memory _calldata) + external + returns (address, bool, bytes memory) + { + address currentActor = _getExactActor(_actorIndex); + + vm.prank(currentActor); + (bool success, bytes memory returnData) = address(_target).call(_calldata); + + return (currentActor, success, returnData); + } + + function initiateActorCall(uint256 _actorIndexSeed, address _target, bytes memory _calldata) + external + returns (address, bool, bytes memory) + { + address currentActor = _getActor(_actorIndexSeed); + + vm.prank(currentActor); + (bool success, bytes memory returnData) = address(_target).call(_calldata); + + return (currentActor, success, returnData); + } + + function fetchActor(uint256 _actorIndexSeed) external view returns (address, uint256) { + uint256 randomActorIndex = bound(_actorIndexSeed, 0, actors.length - 1); + address randomActor = actors[randomActorIndex]; + + return (randomActor, randomActorIndex); + } + + function _getActor(uint256 _actorIndexSeed) internal view returns (address) { + return actors[bound(_actorIndexSeed, 0, actors.length - 1)]; + } + + function _getExactActor(uint256 _actorIndex) internal view returns (address) { + return actors[_actorIndex]; + } +} diff --git a/test/invariant/util/Strategy.sol b/test/invariant/util/Strategy.sol new file mode 100644 index 00000000..ee82c129 --- /dev/null +++ b/test/invariant/util/Strategy.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Test} from "forge-std/Test.sol"; + +contract Strategy is Test { + address[] public strategies; + + function includeStrategy(address _strategy) external { + strategies.push(_strategy); + } + + function fetchStrategy(uint256 _strategyIndexSeed) external view returns (address) { + return strategies[bound(_strategyIndexSeed, 0, strategies.length - 1)]; + } + + function fetchExactStrategy(uint256 _strategyIndex) external view returns (address) { + return strategies[_strategyIndex]; + } +} From 28a1622d99c6a64cf7228261605593540ae09dfb Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:18:56 +0300 Subject: [PATCH 206/316] chore: add invariants run to github actions --- .github/workflows/test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 751c33a9..2538f892 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,4 +36,7 @@ jobs: run: FOUNDRY_PROFILE=ci_fuzz forge test -vv - name: Run foundry coverage - run: FOUNDRY_PROFILE=coverage forge coverage --report summary \ No newline at end of file + run: FOUNDRY_PROFILE=coverage forge coverage --report summary + + - name: Run foundry invariants + run: forge clean && FOUNDRY_PROFILE=invariant forge test \ No newline at end of file From 980d11740d7dadd213a364f59b9131161543a8c3 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:11:11 +0300 Subject: [PATCH 207/316] more invariants; no removing strategy with allocated amount --- src/core/lib/ErrorsLib.sol | 1 + src/core/module/AllocationPoints.sol | 1 + src/plugin/Rebalancer.sol | 31 +++++----- .../EulerAggregationLayerInvariants.t.sol | 27 +++++---- .../handler/EulerAggregationLayerHandler.sol | 10 +++- test/invariant/handler/RebalancerHandler.sol | 60 +++++++++++++++++++ 6 files changed, 103 insertions(+), 27 deletions(-) create mode 100644 test/invariant/handler/RebalancerHandler.sol diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index fe7baa39..29e752ff 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -27,4 +27,5 @@ library ErrorsLib { error InvalidPlugin(); error NotRebalancer(); error InvalidAllocationPoints(); + error CanNotRemoveStartegyWithAllocatedAmount(); } diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index d70703f1..da7fd638 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -101,6 +101,7 @@ abstract contract AllocationPointsModule is Shared { if (!strategyStorage.active) { revert Errors.AlreadyRemoved(); } + if(strategyStorage.allocated > 0) revert Errors.CanNotRemoveStartegyWithAllocatedAmount(); _callHooksTarget(REMOVE_STRATEGY, msg.sender); diff --git a/src/plugin/Rebalancer.sol b/src/plugin/Rebalancer.sol index cef89eec..cfef3d14 100644 --- a/src/plugin/Rebalancer.sol +++ b/src/plugin/Rebalancer.sol @@ -19,13 +19,13 @@ contract Rebalancer { ); /// @notice Rebalance strategies allocation for a specific curated vault. - /// @param _curatedVault Curated vault address. + /// @param _aggregationLayer Aggregation layer vault address. /// @param _strategies Strategies addresses. - function executeRebalance(address _curatedVault, address[] calldata _strategies) external { - IEulerAggregationLayer(_curatedVault).gulp(); + function executeRebalance(address _aggregationLayer, address[] calldata _strategies) external { + IEulerAggregationLayer(_aggregationLayer).gulp(); for (uint256 i; i < _strategies.length; ++i) { - _rebalance(_curatedVault, _strategies[i]); + _rebalance(_aggregationLayer, _strategies[i]); } } @@ -33,18 +33,18 @@ contract Rebalancer { /// If current allocation is less than target allocation, the aggregator will: /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash /// - If all the available cash is greater than the max deposit, deposit the max deposit - /// @param _curatedVault Curated vault address. + /// @param _aggregationLayer Aggregation layer vault address. /// @param _strategy Strategy address. - function _rebalance(address _curatedVault, address _strategy) private { + function _rebalance(address _aggregationLayer, address _strategy) private { if (_strategy == address(0)) { return; //nothing to rebalance as that's the cash reserve } IEulerAggregationLayer.Strategy memory strategyData = - IEulerAggregationLayer(_curatedVault).getStrategy(_strategy); + IEulerAggregationLayer(_aggregationLayer).getStrategy(_strategy); - uint256 totalAllocationPointsCache = IEulerAggregationLayer(_curatedVault).totalAllocationPoints(); - uint256 totalAssetsAllocatableCache = IEulerAggregationLayer(_curatedVault).totalAssetsAllocatable(); + uint256 totalAllocationPointsCache = IEulerAggregationLayer(_aggregationLayer).totalAllocationPoints(); + uint256 totalAssetsAllocatableCache = IEulerAggregationLayer(_aggregationLayer).totalAssetsAllocatable(); uint256 targetAllocation = totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; @@ -56,7 +56,7 @@ contract Rebalancer { // Withdraw amountToRebalance = strategyData.allocated - targetAllocation; - uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(_curatedVault); + uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(_aggregationLayer); if (amountToRebalance > maxWithdraw) { amountToRebalance = maxWithdraw; } @@ -65,9 +65,10 @@ contract Rebalancer { } else if (strategyData.allocated < targetAllocation) { // Deposit uint256 targetCash = totalAssetsAllocatableCache - * IEulerAggregationLayer(_curatedVault).getStrategy(address(0)).allocationPoints + * IEulerAggregationLayer(_aggregationLayer).getStrategy(address(0)).allocationPoints / totalAllocationPointsCache; - uint256 currentCash = totalAssetsAllocatableCache - IEulerAggregationLayer(_curatedVault).totalAllocated(); + uint256 currentCash = + totalAssetsAllocatableCache - IEulerAggregationLayer(_aggregationLayer).totalAllocated(); // Calculate available cash to put in strategies uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; @@ -77,7 +78,7 @@ contract Rebalancer { amountToRebalance = cashAvailable; } - uint256 maxDeposit = IERC4626(_strategy).maxDeposit(_curatedVault); + uint256 maxDeposit = IERC4626(_strategy).maxDeposit(_aggregationLayer); if (amountToRebalance > maxDeposit) { amountToRebalance = maxDeposit; } @@ -89,8 +90,8 @@ contract Rebalancer { isDeposit = true; } - IEulerAggregationLayer(_curatedVault).rebalance(_strategy, amountToRebalance, isDeposit); + IEulerAggregationLayer(_aggregationLayer).rebalance(_strategy, amountToRebalance, isDeposit); - emit ExecuteRebalance(_curatedVault, _strategy, strategyData.allocated, targetAllocation, amountToRebalance); + emit ExecuteRebalance(_aggregationLayer, _strategy, strategyData.allocated, targetAllocation, amountToRebalance); } } diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 24926966..35fed47d 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -10,12 +10,14 @@ import { import {Actor} from "./util/Actor.sol"; import {Strategy} from "./util/Strategy.sol"; import {EulerAggregationLayerHandler} from "./handler/EulerAggregationLayerHandler.sol"; +import {RebalancerHandler} from "./handler/RebalancerHandler.sol"; contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { Actor internal actorUtil; Strategy internal strategyUtil; EulerAggregationLayerHandler internal eulerAggregationLayerHandler; + RebalancerHandler internal rebalancerHandler; function setUp() public override { super.setUp(); @@ -30,7 +32,11 @@ contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { strategyUtil.includeStrategy(address(eTST)); eulerAggregationLayerHandler = new EulerAggregationLayerHandler(eulerAggregationLayer, actorUtil, strategyUtil); + rebalancerHandler = + new RebalancerHandler(eulerAggregationLayer, rebalancer, actorUtil, strategyUtil, withdrawalQueue); + targetContract(address(eulerAggregationLayerHandler)); + targetContract(address(rebalancerHandler)); } function invariant_totalAllocationPoints() public view { @@ -63,16 +69,17 @@ contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { } } - // function invariant_totalAssetsDeposited() public view { - // address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); + function invariant_totalAllocated() public view { + address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); + + (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = + IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); - // (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = - // IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); + uint256 aggregatedAllocatedAmount; + for (uint256 i; i < withdrawalQueueLength; i++) { + aggregatedAllocatedAmount += (eulerAggregationLayer.getStrategy(withdrawalQueueArray[i])).allocated; + } - // uint256 aggregatedAllocatedAmount; - // for (uint256 i; i < withdrawalQueueLength; i++) { - // aggregatedAllocatedAmount += - // (eulerAggregationLayer.getStrategy(withdrawalQueueArray[i])).allocated; - // } - // } + assertEq(eulerAggregationLayer.totalAllocated(), aggregatedAllocatedAmount); + } } diff --git a/test/invariant/handler/EulerAggregationLayerHandler.sol b/test/invariant/handler/EulerAggregationLayerHandler.sol index c2ae2afc..3a29a4bf 100644 --- a/test/invariant/handler/EulerAggregationLayerHandler.sol +++ b/test/invariant/handler/EulerAggregationLayerHandler.sol @@ -101,7 +101,7 @@ contract EulerAggregationLayerHandler is Test { assertEq(strategyAfter.allocationPoints, 0); } - function deposit(uint256 _actorIndexSeed, uint256 _assets, address _receiver) public { + function deposit(uint256 _actorIndexSeed, uint256 _assets, address _receiver) external { vm.assume(_receiver != address(0)); (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); @@ -120,7 +120,7 @@ contract EulerAggregationLayerHandler is Test { assertEq(eulerAggLayer.totalAssetsDeposited(), ghost_totalAssetsDeposited); } - function mint(uint256 _actorIndexSeed, uint256 _shares, address _receiver) public { + function mint(uint256 _actorIndexSeed, uint256 _shares, address _receiver) external { vm.assume(_receiver != address(0)); (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); @@ -140,6 +140,12 @@ contract EulerAggregationLayerHandler is Test { assertEq(eulerAggLayer.totalAssetsDeposited(), ghost_totalAssetsDeposited); } + function harvest(uint256 _actorIndexSeed) external { + (, success, returnData) = actorUtil.initiateActorCall( + _actorIndexSeed, address(eulerAggLayer), abi.encodeWithSelector(IEulerAggregationLayer.harvest.selector) + ); + } + function _fillBalance(address _actor, address _asset, uint256 _amount) internal { TestERC20 asset = TestERC20(_asset); diff --git a/test/invariant/handler/RebalancerHandler.sol b/test/invariant/handler/RebalancerHandler.sol new file mode 100644 index 00000000..0ba5c881 --- /dev/null +++ b/test/invariant/handler/RebalancerHandler.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + Test, + EulerAggregationLayerBase, + EulerAggregationLayer, + console2, + EVault, + IEVault, + IRMTestDefault, + TestERC20, + IEulerAggregationLayer, + ErrorsLib, + IERC4626, + Rebalancer, + WithdrawalQueue +} from "../../common/EulerAggregationLayerBase.t.sol"; +import {Actor} from "../util/Actor.sol"; +import {Strategy} from "../util/Strategy.sol"; + +contract RebalancerHandler is Test { + Actor internal actorUtil; + Strategy internal strategyUtil; + EulerAggregationLayer internal eulerAggLayer; + Rebalancer internal rebalancer; + WithdrawalQueue internal withdrawalQueue; + + // last function call state + address currentActor; + uint256 currentActorIndex; + bool success; + bytes returnData; + + constructor( + EulerAggregationLayer _eulerAggLayer, + Rebalancer _rebalancer, + Actor _actor, + Strategy _strategy, + WithdrawalQueue _withdrawalQueue + ) { + eulerAggLayer = _eulerAggLayer; + actorUtil = _actor; + strategyUtil = _strategy; + rebalancer = _rebalancer; + withdrawalQueue = _withdrawalQueue; + } + + function executeRebalance(uint256 _actorIndexSeed) external { + (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); + + (address[] memory strategiesToRebalance,) = withdrawalQueue.getWithdrawalQueueArray(); + + (currentActor, success, returnData) = actorUtil.initiateActorCall( + _actorIndexSeed, + address(rebalancer), + abi.encodeWithSelector(Rebalancer.executeRebalance.selector, address(eulerAggLayer), strategiesToRebalance) + ); + } +} From d32af2ab720a52eadb6cc9f54081fdb3f855ecf6 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:11:32 +0300 Subject: [PATCH 208/316] lint: --- src/core/module/AllocationPoints.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index da7fd638..3953149a 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -101,7 +101,7 @@ abstract contract AllocationPointsModule is Shared { if (!strategyStorage.active) { revert Errors.AlreadyRemoved(); } - if(strategyStorage.allocated > 0) revert Errors.CanNotRemoveStartegyWithAllocatedAmount(); + if (strategyStorage.allocated > 0) revert Errors.CanNotRemoveStartegyWithAllocatedAmount(); _callHooksTarget(REMOVE_STRATEGY, msg.sender); From 1f23c58423da897034001d70a97ec7b44e4b0f53 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:24:06 +0300 Subject: [PATCH 209/316] fix CI --- src/plugin/WithdrawalQueue.sol | 1 + ...positRebalanceHarvestWithdrawE2ETest.t.sol | 19 +++++++++++++++---- .../fuzz/AdjustAllocationPointsFuzzTest.t.sol | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index fa122a92..9c619ce9 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -145,6 +145,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue } } + // is this possible? if (_availableAssets < _assets) { revert NotEnoughAssets(); } diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 0c6f7f8b..15fa6670 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -612,15 +612,26 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { eulerAggregationLayer.harvest(); vm.warp(block.timestamp + 2 weeks); - vm.prank(manager); - eulerAggregationLayer.removeStrategy(address(eTSTsecondary)); - + // vm.prank(manager); + // eulerAggregationLayer.removeStrategy(address(eTSTsecondary)); + + // vm.mockCall( + // address(eTST), + // abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), + // abi.encode(0) + // ); + // vm.mockCall( + // address(eTSTsecondary), + // abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), + // abi.encode(0) + // ); { uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); vm.prank(user1); - vm.expectRevert(WithdrawalQueue.NotEnoughAssets.selector); + // vm.expectRevert(WithdrawalQueue.NotEnoughAssets.selector); eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); } + // vm.clearMockedCalls(); } } diff --git a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol index 531d7253..be8c0981 100644 --- a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol +++ b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol @@ -16,7 +16,7 @@ contract AdjustAllocationsPointsFuzzTest is EulerAggregationLayerBase { } function testFuzzAdjustAllocationPoints(uint256 _newAllocationPoints) public { - _newAllocationPoints = bound(_newAllocationPoints, 0, type(uint120).max); + _newAllocationPoints = bound(_newAllocationPoints, 1, type(uint120).max); uint256 strategyAllocationPoints = (eulerAggregationLayer.getStrategy(address(eTST))).allocationPoints; uint256 totalAllocationPointsBefore = eulerAggregationLayer.totalAllocationPoints(); From 9c91401acdbcdf809cf843a4ce566f743adbf82a Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:42:51 +0300 Subject: [PATCH 210/316] more invariants; fix: reset cap when removing strategy --- src/core/module/AllocationPoints.sol | 1 + .../EulerAggregationLayerInvariants.t.sol | 45 ++++++------ .../handler/EulerAggregationLayerHandler.sol | 69 +++++++++++++++++-- 3 files changed, 89 insertions(+), 26 deletions(-) diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index 3953149a..4fd04de6 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -108,6 +108,7 @@ abstract contract AllocationPointsModule is Shared { $.totalAllocationPoints -= strategyStorage.allocationPoints; strategyStorage.active = false; strategyStorage.allocationPoints = 0; + strategyStorage.cap = 0; // remove from withdrawalQueue IWithdrawalQueue($.withdrawalQueue).removeStrategyFromWithdrawalQueue(_strategy); diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 35fed47d..e7654549 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -39,35 +39,35 @@ contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { targetContract(address(rebalancerHandler)); } - function invariant_totalAllocationPoints() public view { - address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); + // function invariant_totalAllocationPoints() public view { + // address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); - (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = - IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); + // (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = + // IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); - uint256 expectedTotalAllocationpoints; - expectedTotalAllocationpoints += (eulerAggregationLayer.getStrategy(address(0))).allocationPoints; - for (uint256 i; i < withdrawalQueueLength; i++) { - expectedTotalAllocationpoints += - (eulerAggregationLayer.getStrategy(withdrawalQueueArray[i])).allocationPoints; - } + // uint256 expectedTotalAllocationpoints; + // expectedTotalAllocationpoints += (eulerAggregationLayer.getStrategy(address(0))).allocationPoints; + // for (uint256 i; i < withdrawalQueueLength; i++) { + // expectedTotalAllocationpoints += + // (eulerAggregationLayer.getStrategy(withdrawalQueueArray[i])).allocationPoints; + // } - assertEq(eulerAggregationLayer.totalAllocationPoints(), expectedTotalAllocationpoints); - } + // assertEq(eulerAggregationLayer.totalAllocationPoints(), expectedTotalAllocationpoints); + // } - function invariant_withdrawalQueue() public view { - address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); + // function invariant_withdrawalQueue() public view { + // address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); - (, uint256 withdrawalQueueLength) = IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); + // (, uint256 withdrawalQueueLength) = IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); - uint256 cashReserveAllocationPoints = (eulerAggregationLayer.getStrategy(address(0))).allocationPoints; + // uint256 cashReserveAllocationPoints = (eulerAggregationLayer.getStrategy(address(0))).allocationPoints; - if (eulerAggregationLayer.totalAllocationPoints() - cashReserveAllocationPoints == 0) { - assertEq(withdrawalQueueLength, 0); - } else { - assertGt(withdrawalQueueLength, 0); - } - } + // if (eulerAggregationLayer.totalAllocationPoints() - cashReserveAllocationPoints == 0) { + // assertEq(withdrawalQueueLength, 0); + // } else { + // assertGt(withdrawalQueueLength, 0); + // } + // } function invariant_totalAllocated() public view { address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); @@ -80,6 +80,7 @@ contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { aggregatedAllocatedAmount += (eulerAggregationLayer.getStrategy(withdrawalQueueArray[i])).allocated; } + console2.log("eulerAggregationLayer.totalAllocated()", eulerAggregationLayer.totalAllocated()); assertEq(eulerAggregationLayer.totalAllocated(), aggregatedAllocatedAmount); } } diff --git a/test/invariant/handler/EulerAggregationLayerHandler.sol b/test/invariant/handler/EulerAggregationLayerHandler.sol index 3a29a4bf..f0a18acb 100644 --- a/test/invariant/handler/EulerAggregationLayerHandler.sol +++ b/test/invariant/handler/EulerAggregationLayerHandler.sol @@ -63,6 +63,25 @@ contract EulerAggregationLayerHandler is Test { assertEq(strategyAfter.allocationPoints, ghost_allocationPoints[strategyAddr]); } + function setStrategyCap(uint256 _strategyIndexSeed, uint256 _cap) external { + address strategyAddr = strategyUtil.fetchStrategy(_strategyIndexSeed); + + IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggLayer.getStrategy(strategyAddr); + + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( + 0, + address(eulerAggLayer), + abi.encodeWithSelector(EulerAggregationLayer.setStrategyCap.selector, strategyAddr, _cap) + ); + + IEulerAggregationLayer.Strategy memory strategyAfter = eulerAggLayer.getStrategy(strategyAddr); + if (success) { + assertEq(strategyAfter.cap, _cap); + } else { + assertEq(strategyAfter.cap, strategyBefore.cap); + } + } + function addStrategy(uint256 _strategyIndexSeed, uint256 _allocationPoints) external { address strategyAddr = strategyUtil.fetchStrategy(_strategyIndexSeed); @@ -96,9 +115,22 @@ contract EulerAggregationLayerHandler is Test { ghost_totalAllocationPoints -= strategyBefore.allocationPoints; ghost_allocationPoints[strategyAddr] = 0; } + IEulerAggregationLayer.Strategy memory strategyAfter = eulerAggLayer.getStrategy(strategyAddr); + assertEq(strategyAfter.allocationPoints, ghost_allocationPoints[strategyAddr]); assertEq(eulerAggLayer.totalAllocationPoints(), ghost_totalAllocationPoints); - assertEq(strategyAfter.allocationPoints, 0); + } + + function harvest(uint256 _actorIndexSeed) external { + (, success, returnData) = actorUtil.initiateActorCall( + _actorIndexSeed, address(eulerAggLayer), abi.encodeWithSelector(IEulerAggregationLayer.harvest.selector) + ); + } + + function updateInterestAccrued(uint256 _actorIndexSeed) external { + (, success, returnData) = actorUtil.initiateActorCall( + _actorIndexSeed, address(eulerAggLayer), abi.encodeWithSelector(EulerAggregationLayer.updateInterestAccrued.selector) + ); } function deposit(uint256 _actorIndexSeed, uint256 _assets, address _receiver) external { @@ -140,10 +172,39 @@ contract EulerAggregationLayerHandler is Test { assertEq(eulerAggLayer.totalAssetsDeposited(), ghost_totalAssetsDeposited); } - function harvest(uint256 _actorIndexSeed) external { - (, success, returnData) = actorUtil.initiateActorCall( - _actorIndexSeed, address(eulerAggLayer), abi.encodeWithSelector(IEulerAggregationLayer.harvest.selector) + function withdraw(uint256 _actorIndexSeed, uint256 _assets, address _receiver) external { + vm.assume(_receiver != address(0)); + + (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); + + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( + currentActorIndex, + address(eulerAggLayer), + abi.encodeWithSelector(IERC4626.withdraw.selector, _assets, _receiver, currentActor) ); + + if (success) { + ghost_totalAssetsDeposited -= _assets; + } + assertEq(eulerAggLayer.totalAssetsDeposited(), ghost_totalAssetsDeposited); + } + + function redeem(uint256 _actorIndexSeed, uint256 _shares, address _receiver) external { + vm.assume(_receiver != address(0)); + + (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); + + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( + currentActorIndex, + address(eulerAggLayer), + abi.encodeWithSelector(IERC4626.redeem.selector, _shares, _receiver, currentActor) + ); + + if (success) { + uint256 assets = abi.decode(returnData, (uint256)); + ghost_totalAssetsDeposited -= assets; + } + assertEq(eulerAggLayer.totalAssetsDeposited(), ghost_totalAssetsDeposited); } function _fillBalance(address _actor, address _asset, uint256 _amount) internal { From 6ee42bc908aad6d426d97ca6c5a89ce362776e37 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:43:04 +0300 Subject: [PATCH 211/316] lint --- test/invariant/handler/EulerAggregationLayerHandler.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/invariant/handler/EulerAggregationLayerHandler.sol b/test/invariant/handler/EulerAggregationLayerHandler.sol index f0a18acb..89e83f91 100644 --- a/test/invariant/handler/EulerAggregationLayerHandler.sol +++ b/test/invariant/handler/EulerAggregationLayerHandler.sol @@ -129,7 +129,9 @@ contract EulerAggregationLayerHandler is Test { function updateInterestAccrued(uint256 _actorIndexSeed) external { (, success, returnData) = actorUtil.initiateActorCall( - _actorIndexSeed, address(eulerAggLayer), abi.encodeWithSelector(EulerAggregationLayer.updateInterestAccrued.selector) + _actorIndexSeed, + address(eulerAggLayer), + abi.encodeWithSelector(EulerAggregationLayer.updateInterestAccrued.selector) ); } From 361e9306e78ecb62ce8025adf3a1c9a7944328d7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:01:33 +0300 Subject: [PATCH 212/316] invariants --- src/core/module/Fee.sol | 4 +- .../EulerAggregationLayerInvariants.t.sol | 44 +++++++++---------- .../handler/EulerAggregationLayerHandler.sol | 22 ++++++++++ 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/core/module/Fee.sol b/src/core/module/Fee.sol index 8afa58e3..adc48fd8 100644 --- a/src/core/module/Fee.sol +++ b/src/core/module/Fee.sol @@ -21,7 +21,7 @@ abstract contract FeeModule is Shared { uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; /// @notice Set performance fee recipient address - /// @notice @param _newFeeRecipient Recipient address + /// @param _newFeeRecipient Recipient address function setFeeRecipient(address _newFeeRecipient) external { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); address feeRecipientCached = $.feeRecipient; @@ -34,7 +34,7 @@ abstract contract FeeModule is Shared { } /// @notice Set performance fee (1e18 == 100%) - /// @notice @param _newFee Fee rate + /// @param _newFee Fee rate function setPerformanceFee(uint256 _newFee) external { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index e7654549..a8e42afd 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -39,35 +39,35 @@ contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { targetContract(address(rebalancerHandler)); } - // function invariant_totalAllocationPoints() public view { - // address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); + function invariant_totalAllocationPoints() public view { + address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); - // (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = - // IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); + (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = + IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); - // uint256 expectedTotalAllocationpoints; - // expectedTotalAllocationpoints += (eulerAggregationLayer.getStrategy(address(0))).allocationPoints; - // for (uint256 i; i < withdrawalQueueLength; i++) { - // expectedTotalAllocationpoints += - // (eulerAggregationLayer.getStrategy(withdrawalQueueArray[i])).allocationPoints; - // } + uint256 expectedTotalAllocationpoints; + expectedTotalAllocationpoints += (eulerAggregationLayer.getStrategy(address(0))).allocationPoints; + for (uint256 i; i < withdrawalQueueLength; i++) { + expectedTotalAllocationpoints += + (eulerAggregationLayer.getStrategy(withdrawalQueueArray[i])).allocationPoints; + } - // assertEq(eulerAggregationLayer.totalAllocationPoints(), expectedTotalAllocationpoints); - // } + assertEq(eulerAggregationLayer.totalAllocationPoints(), expectedTotalAllocationpoints); + } - // function invariant_withdrawalQueue() public view { - // address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); + function invariant_withdrawalQueue() public view { + address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); - // (, uint256 withdrawalQueueLength) = IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); + (, uint256 withdrawalQueueLength) = IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); - // uint256 cashReserveAllocationPoints = (eulerAggregationLayer.getStrategy(address(0))).allocationPoints; + uint256 cashReserveAllocationPoints = (eulerAggregationLayer.getStrategy(address(0))).allocationPoints; - // if (eulerAggregationLayer.totalAllocationPoints() - cashReserveAllocationPoints == 0) { - // assertEq(withdrawalQueueLength, 0); - // } else { - // assertGt(withdrawalQueueLength, 0); - // } - // } + if (eulerAggregationLayer.totalAllocationPoints() - cashReserveAllocationPoints == 0) { + assertEq(withdrawalQueueLength, 0); + } else { + assertGt(withdrawalQueueLength, 0); + } + } function invariant_totalAllocated() public view { address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); diff --git a/test/invariant/handler/EulerAggregationLayerHandler.sol b/test/invariant/handler/EulerAggregationLayerHandler.sol index 89e83f91..a821b3f8 100644 --- a/test/invariant/handler/EulerAggregationLayerHandler.sol +++ b/test/invariant/handler/EulerAggregationLayerHandler.sol @@ -43,6 +43,28 @@ contract EulerAggregationLayerHandler is Test { ghost_allocationPoints[address(0)] = ghost_totalAllocationPoints; } + function setFeeRecipient(address _newFeeRecipient) external { + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( + 0, + address(eulerAggLayer), + abi.encodeWithSelector(EulerAggregationLayer.setFeeRecipient.selector, _newFeeRecipient) + ); + + (address feeRecipient,) = eulerAggLayer.performanceFeeConfig(); + + assertEq(feeRecipient, _newFeeRecipient); + } + + function setPerformanceFee(uint256 _newFee) external { + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( + 0, address(eulerAggLayer), abi.encodeWithSelector(EulerAggregationLayer.setPerformanceFee.selector, _newFee) + ); + + (, uint256 fee) = eulerAggLayer.performanceFeeConfig(); + + assertEq(_newFee, fee); + } + function adjustAllocationPoints(uint256 _strategyIndexSeed, uint256 _newPoints) external { address strategyAddr = strategyUtil.fetchStrategy(_strategyIndexSeed); From 95a58963d9087888dde4b532e6aaeb4b9bf23d26 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:55:51 +0300 Subject: [PATCH 213/316] more invariants --- .../EulerAggregationLayerInvariants.t.sol | 64 ++++++++++++++++--- .../handler/EulerAggregationLayerHandler.sol | 54 ++++++++++++++-- test/invariant/handler/RebalancerHandler.sol | 2 - .../handler/WithdrawalQueueHandler.sol | 53 +++++++++++++++ test/invariant/util/Actor.sol | 2 +- 5 files changed, 158 insertions(+), 17 deletions(-) create mode 100644 test/invariant/handler/WithdrawalQueueHandler.sol diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index a8e42afd..4fa1b3a4 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -5,12 +5,14 @@ import { EulerAggregationLayerBase, EulerAggregationLayer, IWithdrawalQueue, - console2 + IEVault, + TestERC20 } from "../common/EulerAggregationLayerBase.t.sol"; import {Actor} from "./util/Actor.sol"; import {Strategy} from "./util/Strategy.sol"; import {EulerAggregationLayerHandler} from "./handler/EulerAggregationLayerHandler.sol"; import {RebalancerHandler} from "./handler/RebalancerHandler.sol"; +import {WithdrawalQueueHandler} from "./handler/WithdrawalQueueHandler.sol"; contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { Actor internal actorUtil; @@ -18,25 +20,43 @@ contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { EulerAggregationLayerHandler internal eulerAggregationLayerHandler; RebalancerHandler internal rebalancerHandler; + WithdrawalQueueHandler internal withdrawalQueueHandler; + + // other strategies + IEVault eTSTsecond; + IEVault eTSTthird; + IEVault eTSTforth; + IEVault eTSTfifth; + IEVault eTSTsixth; function setUp() public override { super.setUp(); actorUtil = new Actor(); - actorUtil.addActor(manager); - actorUtil.addActor(deployer); - actorUtil.addActor(user1); - actorUtil.addActor(user2); + actorUtil.includeActor(manager); + actorUtil.includeActor(deployer); + actorUtil.includeActor(user1); + actorUtil.includeActor(user2); strategyUtil = new Strategy(); strategyUtil.includeStrategy(address(eTST)); - - eulerAggregationLayerHandler = new EulerAggregationLayerHandler(eulerAggregationLayer, actorUtil, strategyUtil); + _deployOtherStrategies(); + strategyUtil.includeStrategy(address(eTSTsecond)); + strategyUtil.includeStrategy(address(eTSTthird)); + strategyUtil.includeStrategy(address(eTSTforth)); + strategyUtil.includeStrategy(address(eTSTfifth)); + strategyUtil.includeStrategy(address(eTSTsixth)); + + eulerAggregationLayerHandler = + new EulerAggregationLayerHandler(eulerAggregationLayer, actorUtil, strategyUtil, withdrawalQueue); rebalancerHandler = new RebalancerHandler(eulerAggregationLayer, rebalancer, actorUtil, strategyUtil, withdrawalQueue); + withdrawalQueueHandler = + new WithdrawalQueueHandler(eulerAggregationLayer, actorUtil, strategyUtil, withdrawalQueue); targetContract(address(eulerAggregationLayerHandler)); targetContract(address(rebalancerHandler)); + targetContract(address(withdrawalQueueHandler)); } function invariant_totalAllocationPoints() public view { @@ -80,7 +100,35 @@ contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { aggregatedAllocatedAmount += (eulerAggregationLayer.getStrategy(withdrawalQueueArray[i])).allocated; } - console2.log("eulerAggregationLayer.totalAllocated()", eulerAggregationLayer.totalAllocated()); assertEq(eulerAggregationLayer.totalAllocated(), aggregatedAllocatedAmount); } + + function invariant_performanceFee() public view { + for (uint256 i; i < eulerAggregationLayerHandler.ghostFeeRecipientsLength(); i++) { + address feeRecipient = eulerAggregationLayerHandler.ghost_feeRecipients(i); + + assertEq( + assetTST.balanceOf(feeRecipient), + eulerAggregationLayerHandler.ghost_accumulatedPerformanceFeePerRecipient(feeRecipient) + ); + } + } + + function _deployOtherStrategies() private { + eTSTsecond = IEVault( + factory.createProxy(address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount)) + ); + eTSTthird = IEVault( + factory.createProxy(address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount)) + ); + eTSTforth = IEVault( + factory.createProxy(address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount)) + ); + eTSTfifth = IEVault( + factory.createProxy(address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount)) + ); + eTSTsixth = IEVault( + factory.createProxy(address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount)) + ); + } } diff --git a/test/invariant/handler/EulerAggregationLayerHandler.sol b/test/invariant/handler/EulerAggregationLayerHandler.sol index a821b3f8..9095eb61 100644 --- a/test/invariant/handler/EulerAggregationLayerHandler.sol +++ b/test/invariant/handler/EulerAggregationLayerHandler.sol @@ -12,8 +12,10 @@ import { TestERC20, IEulerAggregationLayer, ErrorsLib, - IERC4626 + IERC4626, + WithdrawalQueue } from "../../common/EulerAggregationLayerBase.t.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {Actor} from "../util/Actor.sol"; import {Strategy} from "../util/Strategy.sol"; @@ -21,11 +23,14 @@ contract EulerAggregationLayerHandler is Test { Actor internal actorUtil; Strategy internal strategyUtil; EulerAggregationLayer internal eulerAggLayer; + WithdrawalQueue internal withdrawalQueue; // ghost vars uint256 ghost_totalAllocationPoints; uint256 ghost_totalAssetsDeposited; mapping(address => uint256) ghost_allocationPoints; + address[] public ghost_feeRecipients; + mapping(address => uint256) public ghost_accumulatedPerformanceFeePerRecipient; // last function call state address currentActor; @@ -33,26 +38,37 @@ contract EulerAggregationLayerHandler is Test { bool success; bytes returnData; - constructor(EulerAggregationLayer _eulerAggLayer, Actor _actor, Strategy _strategy) { + constructor( + EulerAggregationLayer _eulerAggLayer, + Actor _actor, + Strategy _strategy, + WithdrawalQueue _withdrawalQueue + ) { eulerAggLayer = _eulerAggLayer; actorUtil = _actor; strategyUtil = _strategy; + withdrawalQueue = _withdrawalQueue; // initiating ghost total allocation points to match count cash reserve. ghost_totalAllocationPoints += eulerAggLayer.totalAllocationPoints(); ghost_allocationPoints[address(0)] = ghost_totalAllocationPoints; } - function setFeeRecipient(address _newFeeRecipient) external { + function setFeeRecipient(string calldata _feeRecipientSeed) external { + address feeRecipientAddr = makeAddr(_feeRecipientSeed); + // vm.assume(_newFeeRecipient != address(0)); + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( 0, address(eulerAggLayer), - abi.encodeWithSelector(EulerAggregationLayer.setFeeRecipient.selector, _newFeeRecipient) + abi.encodeWithSelector(EulerAggregationLayer.setFeeRecipient.selector, feeRecipientAddr) ); + if (success) { + ghost_feeRecipients.push(feeRecipientAddr); + } (address feeRecipient,) = eulerAggLayer.performanceFeeConfig(); - - assertEq(feeRecipient, _newFeeRecipient); + assertEq(feeRecipient, feeRecipientAddr); } function setPerformanceFee(uint256 _newFee) external { @@ -144,9 +160,31 @@ contract EulerAggregationLayerHandler is Test { } function harvest(uint256 _actorIndexSeed) external { + // check if performance fee is on; store received fee per recipient if call is succesfull + (address feeRecipient, uint256 performanceFee) = eulerAggLayer.performanceFeeConfig(); + uint256 accumulatedPerformanceFee; + if (feeRecipient != address(0) && performanceFee > 0) { + accumulatedPerformanceFee = ghost_accumulatedPerformanceFeePerRecipient[feeRecipient]; + (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = + withdrawalQueue.getWithdrawalQueueArray(); + + for (uint256 i; i < withdrawalQueueLength; i++) { + uint256 allocated = (eulerAggLayer.getStrategy(withdrawalQueueArray[i])).allocated; + uint256 underlying = IERC4626(withdrawalQueueArray[i]).maxWithdraw(address(eulerAggLayer)); + if (underlying > allocated) { + uint256 yield = underlying - allocated; + accumulatedPerformanceFee += Math.mulDiv(yield, performanceFee, 1e18, Math.Rounding.Floor); + } + } + } + (, success, returnData) = actorUtil.initiateActorCall( _actorIndexSeed, address(eulerAggLayer), abi.encodeWithSelector(IEulerAggregationLayer.harvest.selector) ); + + if (success) { + ghost_accumulatedPerformanceFeePerRecipient[feeRecipient] = accumulatedPerformanceFee; + } } function updateInterestAccrued(uint256 _actorIndexSeed) external { @@ -231,6 +269,10 @@ contract EulerAggregationLayerHandler is Test { assertEq(eulerAggLayer.totalAssetsDeposited(), ghost_totalAssetsDeposited); } + function ghostFeeRecipientsLength() external view returns (uint256) { + return ghost_feeRecipients.length; + } + function _fillBalance(address _actor, address _asset, uint256 _amount) internal { TestERC20 asset = TestERC20(_asset); diff --git a/test/invariant/handler/RebalancerHandler.sol b/test/invariant/handler/RebalancerHandler.sol index 0ba5c881..f0fdb9b4 100644 --- a/test/invariant/handler/RebalancerHandler.sol +++ b/test/invariant/handler/RebalancerHandler.sol @@ -5,7 +5,6 @@ import { Test, EulerAggregationLayerBase, EulerAggregationLayer, - console2, EVault, IEVault, IRMTestDefault, @@ -50,7 +49,6 @@ contract RebalancerHandler is Test { (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); (address[] memory strategiesToRebalance,) = withdrawalQueue.getWithdrawalQueueArray(); - (currentActor, success, returnData) = actorUtil.initiateActorCall( _actorIndexSeed, address(rebalancer), diff --git a/test/invariant/handler/WithdrawalQueueHandler.sol b/test/invariant/handler/WithdrawalQueueHandler.sol new file mode 100644 index 00000000..0c9e165f --- /dev/null +++ b/test/invariant/handler/WithdrawalQueueHandler.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + Test, + EulerAggregationLayerBase, + EulerAggregationLayer, + console2, + EVault, + IEVault, + IRMTestDefault, + TestERC20, + IEulerAggregationLayer, + ErrorsLib, + IERC4626, + Rebalancer, + WithdrawalQueue +} from "../../common/EulerAggregationLayerBase.t.sol"; +import {Actor} from "../util/Actor.sol"; +import {Strategy} from "../util/Strategy.sol"; + +contract WithdrawalQueueHandler is Test { + Actor internal actorUtil; + Strategy internal strategyUtil; + EulerAggregationLayer internal eulerAggLayer; + WithdrawalQueue internal withdrawalQueue; + + // last function call state + address currentActor; + uint256 currentActorIndex; + bool success; + bytes returnData; + + constructor( + EulerAggregationLayer _eulerAggLayer, + Actor _actor, + Strategy _strategy, + WithdrawalQueue _withdrawalQueue + ) { + eulerAggLayer = _eulerAggLayer; + actorUtil = _actor; + strategyUtil = _strategy; + withdrawalQueue = _withdrawalQueue; + } + + function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external { + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( + 0, + address(withdrawalQueue), + abi.encodeWithSelector(WithdrawalQueue.reorderWithdrawalQueue.selector, _index1, _index2) + ); + } +} diff --git a/test/invariant/util/Actor.sol b/test/invariant/util/Actor.sol index d00f1791..149aa9e1 100644 --- a/test/invariant/util/Actor.sol +++ b/test/invariant/util/Actor.sol @@ -7,7 +7,7 @@ contract Actor is Test { /// @dev actor[0] will always be a manager address that have access to all EulerAggregationLayer roles. address[] public actors; - function addActor(address _actor) external { + function includeActor(address _actor) external { actors.push(_actor); } From 4a9c003abcb049bb1985c65f5f871fce922baac0 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:51:54 +0300 Subject: [PATCH 214/316] add gulp to handler --- .../handler/EulerAggregationLayerHandler.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/invariant/handler/EulerAggregationLayerHandler.sol b/test/invariant/handler/EulerAggregationLayerHandler.sol index 9095eb61..1cfb811d 100644 --- a/test/invariant/handler/EulerAggregationLayerHandler.sol +++ b/test/invariant/handler/EulerAggregationLayerHandler.sol @@ -195,6 +195,19 @@ contract EulerAggregationLayerHandler is Test { ); } + function gulp(uint256 _actorIndexSeed) external { + (, success, returnData) = actorUtil.initiateActorCall( + _actorIndexSeed, address(eulerAggLayer), abi.encodeWithSelector(EulerAggregationLayer.gulp.selector) + ); + + if (success) { + assertEq( + eulerAggLayer.totalAssetsAllocatable(), + eulerAggLayer.totalAssetsDeposited() + (eulerAggLayer.getAggregationVaultSavingRate()).interestLeft + ); + } + } + function deposit(uint256 _actorIndexSeed, uint256 _assets, address _receiver) external { vm.assume(_receiver != address(0)); From e61207f4f34f6574b5d2842ce4092c05da65926a Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:22:58 +0300 Subject: [PATCH 215/316] fix: minor comments from certora --- src/core/Dispatch.sol | 4 +--- src/core/EulerAggregationLayer.sol | 3 ++- src/core/common/Shared.sol | 2 +- src/core/module/Fee.sol | 4 +--- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/core/Dispatch.sol b/src/core/Dispatch.sol index 78534a08..7d79cda5 100644 --- a/src/core/Dispatch.sol +++ b/src/core/Dispatch.sol @@ -22,9 +22,7 @@ abstract contract Dispatch is RewardsModule, HooksModule { /// @param _hooksModule Address of Hooks module. /// @param _feeModule Address of Fee module. /// @param _allocationPointsModule Address of AllocationPoints module. - constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) - Shared() - { + constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) { MODULE_REWARDS = _rewardsModule; MODULE_HOOKS = _hooksModule; MODULE_FEE = _feeModule; diff --git a/src/core/EulerAggregationLayer.sol b/src/core/EulerAggregationLayer.sol index 2828e288..c6f4ba4d 100644 --- a/src/core/EulerAggregationLayer.sol +++ b/src/core/EulerAggregationLayer.sol @@ -62,6 +62,7 @@ contract EulerAggregationLayer is function init(InitParams calldata _initParams) external initializer { __ERC4626_init_unchained(IERC20(_initParams.asset)); __ERC20_init_unchained(_initParams.name, _initParams.symbol); + __AccessControlEnumerable_init(); if (_initParams.initialCashAllocationPoints == 0) revert Errors.InitialAllocationPointsZero(); @@ -214,7 +215,7 @@ contract EulerAggregationLayer is // Do required approval (safely) and deposit IERC20(asset()).safeIncreaseAllowance(_strategy, _amountToRebalance); IERC4626(_strategy).deposit(_amountToRebalance, address(this)); - $.strategies[_strategy].allocated = uint120(strategyData.allocated + _amountToRebalance); + $.strategies[_strategy].allocated = (strategyData.allocated + _amountToRebalance).toUint120(); $.totalAllocated += _amountToRebalance; } else { IERC4626(_strategy).withdraw(_amountToRebalance, address(this), address(this)); diff --git a/src/core/common/Shared.sol b/src/core/common/Shared.sol index c503a595..5fc2a7f9 100644 --- a/src/core/common/Shared.sol +++ b/src/core/common/Shared.sol @@ -12,7 +12,7 @@ import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; /// @title Shared contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) -contract Shared { +abstract contract Shared { using HooksLib for uint32; uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1; diff --git a/src/core/module/Fee.sol b/src/core/module/Fee.sol index adc48fd8..b013d682 100644 --- a/src/core/module/Fee.sol +++ b/src/core/module/Fee.sol @@ -6,8 +6,6 @@ import {IBalanceForwarder} from "../interface/IBalanceForwarder.sol"; import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -// contracts -import {Shared} from "../common/Shared.sol"; // libs import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; @@ -16,7 +14,7 @@ import {EventsLib as Events} from "../lib/EventsLib.sol"; /// @title FeeModule contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) -abstract contract FeeModule is Shared { +abstract contract FeeModule { /// @dev The maximum performanceFee the vault can have is 50% uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; From bb260c21a63e1a851591cf483b6294f898840e77 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:41:08 +0300 Subject: [PATCH 216/316] forge install: properties v1.0.0 --- .gitmodules | 3 +++ lib/properties | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/properties diff --git a/.gitmodules b/.gitmodules index 68a9c6bf..c8d6cdb7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -20,3 +20,6 @@ [submodule "lib/erc4626-tests"] path = lib/erc4626-tests url = https://github.com/a16z/erc4626-tests +[submodule "lib/properties"] + path = lib/properties + url = https://github.com/crytic/properties diff --git a/lib/properties b/lib/properties new file mode 160000 index 00000000..bb1b7854 --- /dev/null +++ b/lib/properties @@ -0,0 +1 @@ +Subproject commit bb1b78542b3f38e4ae56cf87389cd3ea94387f48 From 6ef8f03c80cd3c671bde4bd6ffec958dc165097f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:39:17 +0300 Subject: [PATCH 217/316] add echidna properties tests --- .github/workflows/echidna.yml | 40 +++++++++++++++++ .gitignore | 6 ++- foundry.toml | 2 +- remappings.txt | 3 +- test/A16zPropertyTests.t.sol | 2 +- test/echidna/CryticERC4626Harness.t.sol | 59 +++++++++++++++++++++++++ test/echidna/config/echidna.config.yaml | 28 ++++++++++++ 7 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/echidna.yml create mode 100644 test/echidna/CryticERC4626Harness.t.sol create mode 100644 test/echidna/config/echidna.config.yaml diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml new file mode 100644 index 00000000..8d2ee55c --- /dev/null +++ b/.github/workflows/echidna.yml @@ -0,0 +1,40 @@ +name: Echidna Test + +on: + push: + branches: + - main + pull_request: + +env: + FOUNDRY_PROFILE: ci + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Compile contracts + run: | + forge build --build-info + + - name: Run Echidna + uses: crytic/echidna-action@v2 + with: + files: test/echidna/CryticERC4626Harness.t.sol + contract: CryticERC4626Harness + crytic-args: --ignore-compile + config: test/echidna/config/echidna.config.yaml \ No newline at end of file diff --git a/.gitignore b/.gitignore index d32ac12c..a9f0d3a5 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,8 @@ lcov.info coverage # gas -.gas-snapshot \ No newline at end of file +.gas-snapshot + +# echidna +crytic-export/ +/test/echidna/_corpus/ \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index b4528b40..663785af 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,7 +5,7 @@ libs = ["lib"] test = 'test' optimizer = true optimizer_runs = 500 -# solc = "0.8.0" +solc = "0.8.24" gas_reports = ["*"] fs_permissions = [{ access = "read", path = "./"}] diff --git a/remappings.txt b/remappings.txt index 4d63589c..7a2b834b 100644 --- a/remappings.txt +++ b/remappings.txt @@ -6,4 +6,5 @@ evk/=lib/euler-vault-kit/ reward-streams=lib/reward-streams/src openzeppelin-contracts/=lib/reward-streams/lib/openzeppelin-contracts/contracts @openzeppelin/=lib/openzeppelin-contracts/ -@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ \ No newline at end of file +@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +crytic-properties/=lib/properties/contracts/ \ No newline at end of file diff --git a/test/A16zPropertyTests.t.sol b/test/A16zPropertyTests.t.sol index 4e33853b..298a7ace 100644 --- a/test/A16zPropertyTests.t.sol +++ b/test/A16zPropertyTests.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -// a16z property tests +// a16z properties tests import {ERC4626Test} from "erc4626-tests/ERC4626.test.sol"; // contracts import {EulerAggregationLayer} from "../src/core/EulerAggregationLayer.sol"; diff --git a/test/echidna/CryticERC4626Harness.t.sol b/test/echidna/CryticERC4626Harness.t.sol new file mode 100644 index 00000000..4662dff1 --- /dev/null +++ b/test/echidna/CryticERC4626Harness.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +// echidna erc-4626 properties tests +import {CryticERC4626PropertyTests} from "crytic-properties/ERC4626/ERC4626PropertyTests.sol"; +// contracts +import {EulerAggregationLayer} from "../../src/core/EulerAggregationLayer.sol"; +import {Rebalancer} from "../../src/plugin/Rebalancer.sol"; +import {Hooks} from "../../src/core/module/Hooks.sol"; +import {Rewards} from "../../src/core/module/Rewards.sol"; +import {Fee} from "../../src/core/module/Fee.sol"; +import {EulerAggregationLayerFactory} from "../../src/core/EulerAggregationLayerFactory.sol"; +import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; +import {AllocationPoints} from "../../src/core/module/AllocationPoints.sol"; +import {TestERC20Token} from "crytic-properties/ERC4626/util/TestERC20Token.sol"; + +contract CryticERC4626Harness is CryticERC4626PropertyTests { + uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; + + // core modules + Rewards rewardsImpl; + Hooks hooksImpl; + Fee feeModuleImpl; + AllocationPoints allocationPointsModuleImpl; + // plugins + Rebalancer rebalancerPlugin; + WithdrawalQueue withdrawalQueuePluginImpl; + + EulerAggregationLayerFactory eulerAggregationLayerFactory; + EulerAggregationLayer eulerAggregationLayer; + + constructor() { + rewardsImpl = new Rewards(); + hooksImpl = new Hooks(); + feeModuleImpl = new Fee(); + allocationPointsModuleImpl = new AllocationPoints(); + + rebalancerPlugin = new Rebalancer(); + withdrawalQueuePluginImpl = new WithdrawalQueue(); + + EulerAggregationLayerFactory.FactoryParams memory factoryParams = EulerAggregationLayerFactory.FactoryParams({ + balanceTracker: address(0), + rewardsModuleImpl: address(rewardsImpl), + hooksModuleImpl: address(hooksImpl), + feeModuleImpl: address(feeModuleImpl), + allocationPointsModuleImpl: address(allocationPointsModuleImpl), + rebalancer: address(rebalancerPlugin), + withdrawalQueueImpl: address(withdrawalQueuePluginImpl) + }); + eulerAggregationLayerFactory = new EulerAggregationLayerFactory(factoryParams); + + TestERC20Token _asset = new TestERC20Token("Test Token", "TT", 18); + address _vault = eulerAggregationLayerFactory.deployEulerAggregationLayer( + address(_asset), "TT_Agg", "TT_Agg", CASH_RESERVE_ALLOCATION_POINTS + ); + + initialize(address(_vault), address(_asset), false); + } +} diff --git a/test/echidna/config/echidna.config.yaml b/test/echidna/config/echidna.config.yaml new file mode 100644 index 00000000..b942abfe --- /dev/null +++ b/test/echidna/config/echidna.config.yaml @@ -0,0 +1,28 @@ +corpusDir: "test/echidna/_corpus/" + +testMode: assertion + +#testLimit is the number of test sequences to run +testLimit: 20000000 + +#codeSize max code size for deployed contratcs (default 24576, per EIP-170) +codeSize: 224576 + +deployer: "0x10000" + +sender: ["0x10000"] + +#stopOnFail makes echidna terminate as soon as any property fails and has been shrunk +stopOnFail: false + +#coverage controls coverage guided testing +coverage: true + +# list of file formats to save coverage reports in; default is all possible formats +coverageFormats: ["lcov", "html"] + +#quiet produces (much) less verbose output +quiet: false + +# concurrent workers +workers: 10 \ No newline at end of file From e0d0e5c276c9a3e9901681403714166a60819433 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:56:26 +0300 Subject: [PATCH 218/316] update echidna config --- .github/workflows/echidna.yml | 1 - test/echidna/config/echidna.config.yaml | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml index 8d2ee55c..f8a8c945 100644 --- a/.github/workflows/echidna.yml +++ b/.github/workflows/echidna.yml @@ -36,5 +36,4 @@ jobs: with: files: test/echidna/CryticERC4626Harness.t.sol contract: CryticERC4626Harness - crytic-args: --ignore-compile config: test/echidna/config/echidna.config.yaml \ No newline at end of file diff --git a/test/echidna/config/echidna.config.yaml b/test/echidna/config/echidna.config.yaml index b942abfe..373212e7 100644 --- a/test/echidna/config/echidna.config.yaml +++ b/test/echidna/config/echidna.config.yaml @@ -25,4 +25,6 @@ coverageFormats: ["lcov", "html"] quiet: false # concurrent workers -workers: 10 \ No newline at end of file +workers: 10 + +cryticArgs: ["--ignore-compile"] \ No newline at end of file From 49e5891d9941b531d7df4e6419176e5358f5eb31 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:10:11 +0300 Subject: [PATCH 219/316] update echidna config --- .github/workflows/echidna.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml index f8a8c945..c704317a 100644 --- a/.github/workflows/echidna.yml +++ b/.github/workflows/echidna.yml @@ -31,9 +31,9 @@ jobs: run: | forge build --build-info - - name: Run Echidna - uses: crytic/echidna-action@v2 - with: - files: test/echidna/CryticERC4626Harness.t.sol - contract: CryticERC4626Harness - config: test/echidna/config/echidna.config.yaml \ No newline at end of file + - name: Install Echidna docker + uses: docker://ghcr.io/crytic/echidna/echidna:latest + + - name: Run Echidna tests + run: | + echidna test/echidna/CryticERC4626Harness.t.sol --contract CryticERC4626Harness --config test/echidna/config/echidna.config.yaml \ No newline at end of file From 24db10355707ebbe22ff3aeebeaa2bde7e523a9a Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:14:47 +0300 Subject: [PATCH 220/316] update echidna config --- .github/workflows/echidna.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml index c704317a..f8a8c945 100644 --- a/.github/workflows/echidna.yml +++ b/.github/workflows/echidna.yml @@ -31,9 +31,9 @@ jobs: run: | forge build --build-info - - name: Install Echidna docker - uses: docker://ghcr.io/crytic/echidna/echidna:latest - - - name: Run Echidna tests - run: | - echidna test/echidna/CryticERC4626Harness.t.sol --contract CryticERC4626Harness --config test/echidna/config/echidna.config.yaml \ No newline at end of file + - name: Run Echidna + uses: crytic/echidna-action@v2 + with: + files: test/echidna/CryticERC4626Harness.t.sol + contract: CryticERC4626Harness + config: test/echidna/config/echidna.config.yaml \ No newline at end of file From e8ed3a967dcc97abaa3749e9e6e9454305e5cd9c Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:16:09 +0300 Subject: [PATCH 221/316] update echidna config --- .github/workflows/echidna.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml index f8a8c945..0beda1e3 100644 --- a/.github/workflows/echidna.yml +++ b/.github/workflows/echidna.yml @@ -36,4 +36,5 @@ jobs: with: files: test/echidna/CryticERC4626Harness.t.sol contract: CryticERC4626Harness - config: test/echidna/config/echidna.config.yaml \ No newline at end of file + config: test/echidna/config/echidna.config.yaml + crytic-args: --ignore-compile \ No newline at end of file From 54fc8fff402f1f211415fc98d1c00f465a560064 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:20:51 +0300 Subject: [PATCH 222/316] update echidna config --- .github/workflows/echidna.yml | 2 +- test/echidna/config/echidna.config.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml index 0beda1e3..40482409 100644 --- a/.github/workflows/echidna.yml +++ b/.github/workflows/echidna.yml @@ -37,4 +37,4 @@ jobs: files: test/echidna/CryticERC4626Harness.t.sol contract: CryticERC4626Harness config: test/echidna/config/echidna.config.yaml - crytic-args: --ignore-compile \ No newline at end of file + crytic-args: --foundry-ignore-compile \ No newline at end of file diff --git a/test/echidna/config/echidna.config.yaml b/test/echidna/config/echidna.config.yaml index 373212e7..06465a9e 100644 --- a/test/echidna/config/echidna.config.yaml +++ b/test/echidna/config/echidna.config.yaml @@ -27,4 +27,4 @@ quiet: false # concurrent workers workers: 10 -cryticArgs: ["--ignore-compile"] \ No newline at end of file +cryticArgs: ["---foundry-ignore-compile"] \ No newline at end of file From 183e27576ba5619de66093f6d7693898ef87ef56 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:28:33 +0300 Subject: [PATCH 223/316] update echidna config --- .github/workflows/echidna.yml | 3 +-- test/echidna/config/echidna.config.yaml | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml index 40482409..e0a6f657 100644 --- a/.github/workflows/echidna.yml +++ b/.github/workflows/echidna.yml @@ -36,5 +36,4 @@ jobs: with: files: test/echidna/CryticERC4626Harness.t.sol contract: CryticERC4626Harness - config: test/echidna/config/echidna.config.yaml - crytic-args: --foundry-ignore-compile \ No newline at end of file + crytic-args: --foundry-ignore-compile --ignore-compile \ No newline at end of file diff --git a/test/echidna/config/echidna.config.yaml b/test/echidna/config/echidna.config.yaml index 06465a9e..b942abfe 100644 --- a/test/echidna/config/echidna.config.yaml +++ b/test/echidna/config/echidna.config.yaml @@ -25,6 +25,4 @@ coverageFormats: ["lcov", "html"] quiet: false # concurrent workers -workers: 10 - -cryticArgs: ["---foundry-ignore-compile"] \ No newline at end of file +workers: 10 \ No newline at end of file From 8ee4b59e33046d9cb5924d5c0ba791b9d0c8b08c Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:33:29 +0300 Subject: [PATCH 224/316] update echidna config --- test/echidna/config/echidna.config.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/echidna/config/echidna.config.yaml b/test/echidna/config/echidna.config.yaml index b942abfe..b7fa5405 100644 --- a/test/echidna/config/echidna.config.yaml +++ b/test/echidna/config/echidna.config.yaml @@ -5,9 +5,6 @@ testMode: assertion #testLimit is the number of test sequences to run testLimit: 20000000 -#codeSize max code size for deployed contratcs (default 24576, per EIP-170) -codeSize: 224576 - deployer: "0x10000" sender: ["0x10000"] From 3eba2f34dba456b82c6d2824202aeb7641e3bef1 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 3 Jul 2024 19:42:22 +0300 Subject: [PATCH 225/316] update --- .github/workflows/echidna.yml | 63 +++++++++---------- .github/workflows/test.yml | 2 +- ....t.sol => CryticERC4626TestsHarness.t.sol} | 2 +- test/echidna/config/echidna.config.yaml | 4 +- 4 files changed, 36 insertions(+), 35 deletions(-) rename test/echidna/{CryticERC4626Harness.t.sol => CryticERC4626TestsHarness.t.sol} (97%) diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml index e0a6f657..d40c1931 100644 --- a/.github/workflows/echidna.yml +++ b/.github/workflows/echidna.yml @@ -1,39 +1,38 @@ -name: Echidna Test +# name: Echidna Test -on: - push: - branches: - - main - pull_request: +# on: +# push: +# branches: +# - main +# pull_request: -env: - FOUNDRY_PROFILE: ci +# env: +# FOUNDRY_PROFILE: ci -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true +# concurrency: +# group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} +# cancel-in-progress: true -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: recursive +# jobs: +# test: +# runs-on: ubuntu-latest +# steps: +# - name: Checkout repository +# uses: actions/checkout@v3 +# with: +# submodules: recursive - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly +# - name: Install Foundry +# uses: foundry-rs/foundry-toolchain@v1 +# with: +# version: nightly - - name: Compile contracts - run: | - forge build --build-info +# - name: Compile contracts +# run: | +# forge build --build-info - - name: Run Echidna - uses: crytic/echidna-action@v2 - with: - files: test/echidna/CryticERC4626Harness.t.sol - contract: CryticERC4626Harness - crytic-args: --foundry-ignore-compile --ignore-compile \ No newline at end of file +# - name: Run Echidna +# uses: crytic/echidna-action@v2 +# with: +# files: test/echidna/CryticERC4626Harness.t.sol +# contract: CryticERC4626Harness \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2538f892..c6d1542b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: version: nightly - name: Run foundry build - run: forge build --force --sizes + run: forge build --force - name: Run foundry fmt check run: forge fmt --check diff --git a/test/echidna/CryticERC4626Harness.t.sol b/test/echidna/CryticERC4626TestsHarness.t.sol similarity index 97% rename from test/echidna/CryticERC4626Harness.t.sol rename to test/echidna/CryticERC4626TestsHarness.t.sol index 4662dff1..572eec44 100644 --- a/test/echidna/CryticERC4626Harness.t.sol +++ b/test/echidna/CryticERC4626TestsHarness.t.sol @@ -14,7 +14,7 @@ import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; import {AllocationPoints} from "../../src/core/module/AllocationPoints.sol"; import {TestERC20Token} from "crytic-properties/ERC4626/util/TestERC20Token.sol"; -contract CryticERC4626Harness is CryticERC4626PropertyTests { +contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; // core modules diff --git a/test/echidna/config/echidna.config.yaml b/test/echidna/config/echidna.config.yaml index b7fa5405..a736524d 100644 --- a/test/echidna/config/echidna.config.yaml +++ b/test/echidna/config/echidna.config.yaml @@ -22,4 +22,6 @@ coverageFormats: ["lcov", "html"] quiet: false # concurrent workers -workers: 10 \ No newline at end of file +workers: 10 + +crytic-args: ["--foundry-ignore-compile", "--ignore-compile"] \ No newline at end of file From 25a2739f67a757d7550fe81aff5c9b98a93c1070 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 4 Jul 2024 11:46:39 +0300 Subject: [PATCH 226/316] update --- README.md | 26 +++---------------------- test/echidna/config/echidna.config.yaml | 2 +- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 9265b455..942b8e71 100644 --- a/README.md +++ b/README.md @@ -39,28 +39,8 @@ $ forge fmt $ forge snapshot ``` -### Anvil +### Run Echidna ```shell -$ anvil -``` - -### Deploy - -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key -``` - -### Cast - -```shell -$ cast -``` - -### Help - -```shell -$ forge --help -$ anvil --help -$ cast --help -``` +$ echidna test/echidna/CryticERC4626TestsHarness.t.sol --contract CryticERC4626TestsHarness --config test/echidna/config/echidna.config.yaml +``` \ No newline at end of file diff --git a/test/echidna/config/echidna.config.yaml b/test/echidna/config/echidna.config.yaml index a736524d..cd3f641b 100644 --- a/test/echidna/config/echidna.config.yaml +++ b/test/echidna/config/echidna.config.yaml @@ -24,4 +24,4 @@ quiet: false # concurrent workers workers: 10 -crytic-args: ["--foundry-ignore-compile", "--ignore-compile"] \ No newline at end of file +cryticArgs: ["--ignore-compile"] \ No newline at end of file From 327729e3f235de42267a65b64450e55b83942feb Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 4 Jul 2024 13:06:51 +0300 Subject: [PATCH 227/316] invariants: gulp --- .github/workflows/test.yml | 2 +- src/core/EulerAggregationLayer.sol | 2 +- test/invariant/EulerAggregationLayerInvariants.t.sol | 10 ++++++++++ test/invariant/handler/RebalancerHandler.sol | 9 ++++++++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c6d1542b..fadad4d5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: version: nightly - name: Run foundry build - run: forge build --force + run: forge build --force --sizes --skip test - name: Run foundry fmt check run: forge fmt --check diff --git a/src/core/EulerAggregationLayer.sol b/src/core/EulerAggregationLayer.sol index c6f4ba4d..0a6307d4 100644 --- a/src/core/EulerAggregationLayer.sol +++ b/src/core/EulerAggregationLayer.sol @@ -489,7 +489,7 @@ contract EulerAggregationLayer is emit Events.Gulp($.interestLeft, $.interestSmearEnd); } - /// @dev Loop through stratgies, aggregated yield and lossm and account for net amount. + /// @dev Loop through stratgies, aggregate positive yield and loss and account for net amount. /// @dev Loss socialization will be taken out from interest left first, if not enough, sozialize on deposits. function _harvest() internal { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 4fa1b3a4..84b0c77e 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -59,6 +59,16 @@ contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { targetContract(address(withdrawalQueueHandler)); } + function invariant_gulp() public { + eulerAggregationLayer.gulp(); + + assertEq( + eulerAggregationLayer.totalAssetsAllocatable(), + eulerAggregationLayer.totalAssetsDeposited() + + (eulerAggregationLayer.getAggregationVaultSavingRate()).interestLeft + ); + } + function invariant_totalAllocationPoints() public view { address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); diff --git a/test/invariant/handler/RebalancerHandler.sol b/test/invariant/handler/RebalancerHandler.sol index f0fdb9b4..d57bc7cb 100644 --- a/test/invariant/handler/RebalancerHandler.sol +++ b/test/invariant/handler/RebalancerHandler.sol @@ -48,11 +48,18 @@ contract RebalancerHandler is Test { function executeRebalance(uint256 _actorIndexSeed) external { (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); - (address[] memory strategiesToRebalance,) = withdrawalQueue.getWithdrawalQueueArray(); + (address[] memory strategiesToRebalance, uint256 strategiesCounter) = withdrawalQueue.getWithdrawalQueueArray(); (currentActor, success, returnData) = actorUtil.initiateActorCall( _actorIndexSeed, address(rebalancer), abi.encodeWithSelector(Rebalancer.executeRebalance.selector, address(eulerAggLayer), strategiesToRebalance) ); + + for (uint256 i; i < strategiesCounter; i++) { + assertEq( + IERC4626(strategiesToRebalance[i]).maxWithdraw(address(eulerAggLayer)), + (eulerAggLayer.getStrategy(strategiesToRebalance[i])).allocated + ); + } } } From 788a09619c2540b6f9e886470af4b053ff3133d6 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 4 Jul 2024 16:02:46 +0300 Subject: [PATCH 228/316] update config --- .github/workflows/test.yml | 4 ++-- foundry.toml | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fadad4d5..49b34ed5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,10 +30,10 @@ jobs: run: forge fmt --check - name: Run foundry tests - run: FOUNDRY_PROFILE=test forge test -vv --gas-report --ast + run: FOUNDRY_PROFILE=test forge test - name: Run foundry fuzzing - run: FOUNDRY_PROFILE=ci_fuzz forge test -vv + run: FOUNDRY_PROFILE=ci_fuzz forge test - name: Run foundry coverage run: FOUNDRY_PROFILE=coverage forge coverage --report summary diff --git a/foundry.toml b/foundry.toml index 663785af..ddfeef13 100644 --- a/foundry.toml +++ b/foundry.toml @@ -22,8 +22,8 @@ ignore = [ ] [profile.test] -no_match_test = "Fuzz" -no_match_contract = "Fuzz" +no_match_test = "(Fuzz|invariant_)" +no_match_contract = "(Fuzz|CryticERC4626TestsHarness)" gas_reports = ["*"] [profile.fuzz] @@ -60,7 +60,9 @@ match_test = "invariant_" [profile.coverage] via_ir = true -no_match_contract = "Script" +no_match_test = "(invariant_)" +no_match_contract = "(Script|CryticERC4626TestsHarness)" +no_match_coverage= "(script|test|fuzz|e2e)" [profile.coverage.optimizer_details] constantOptimizer = true From 9369073bdedd284053e6cd43c3694b9615a8a6c3 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 4 Jul 2024 17:36:10 +0300 Subject: [PATCH 229/316] rename to Euler Aggregation Vault --- ...ionLayer.sol => EulerAggregationVault.sol} | 18 +- ...y.sol => EulerAggregationVaultFactory.sol} | 24 +- ...onLayer.sol => IEulerAggregationVault.sol} | 4 +- src/core/lib/EventsLib.sol | 2 +- src/core/lib/StorageLib.sol | 6 +- src/core/module/AllocationPoints.sol | 8 +- src/plugin/Rebalancer.sol | 36 +- src/plugin/WithdrawalQueue.sol | 8 +- test/A16zPropertyTests.t.sol | 14 +- ....t.sol => EulerAggregationVaultBase.t.sol} | 84 ++-- test/e2e/BalanceForwarderE2ETest.t.sol | 136 +++--- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 390 +++++++++--------- test/e2e/HarvestRedeemE2ETest.t.sol | 104 ++--- test/e2e/HooksE2ETest.t.sol | 50 +-- test/e2e/PerformanceFeeE2ETest.t.sol | 96 ++--- test/e2e/StrategyCapE2ETest.t.sol | 96 ++--- test/e2e/StrategyRewardsE2ETest.t.sol | 20 +- test/echidna/CryticERC4626TestsHarness.t.sol | 14 +- .../fuzz/AdjustAllocationPointsFuzzTest.t.sol | 22 +- .../DepositWithdrawMintBurnFuzzTest.t.sol | 68 +-- .../EulerAggregationLayerInvariants.t.sol | 56 +-- ...r.sol => EulerAggregationVaultHandler.sol} | 106 ++--- test/invariant/handler/RebalancerHandler.sol | 20 +- .../handler/WithdrawalQueueHandler.sol | 14 +- test/invariant/util/Actor.sol | 2 +- test/unit/AddStrategyTest.t.sol | 12 +- test/unit/AdjustAllocationPointsTest.t.sol | 22 +- test/unit/GulpTest.t.sol | 118 +++--- test/unit/HarvestTest.t.sol | 176 ++++---- test/unit/RebalanceTest.t.sol | 222 +++++----- test/unit/RemoveStrategy.t.sol | 38 +- test/unit/ReorderWithdrawalQueueTest.t.sol | 16 +- test/unit/SetRebalancerTest.t.sol | 10 +- test/unit/SetWithdrawalQueueTest.t.sol | 10 +- 34 files changed, 1011 insertions(+), 1011 deletions(-) rename src/core/{EulerAggregationLayer.sol => EulerAggregationVault.sol} (98%) rename src/core/{EulerAggregationLayerFactory.sol => EulerAggregationVaultFactory.sol} (84%) rename src/core/interface/{IEulerAggregationLayer.sol => IEulerAggregationVault.sol} (96%) rename test/common/{EulerAggregationLayerBase.t.sol => EulerAggregationVaultBase.t.sol} (56%) rename test/invariant/handler/{EulerAggregationLayerHandler.sol => EulerAggregationVaultHandler.sol} (72%) diff --git a/src/core/EulerAggregationLayer.sol b/src/core/EulerAggregationVault.sol similarity index 98% rename from src/core/EulerAggregationLayer.sol rename to src/core/EulerAggregationVault.sol index 0a6307d4..d5988d47 100644 --- a/src/core/EulerAggregationLayer.sol +++ b/src/core/EulerAggregationVault.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; -import {IEulerAggregationLayer} from "./interface/IEulerAggregationLayer.sol"; +import {IEulerAggregationVault} from "./interface/IEulerAggregationVault.sol"; import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; // contracts import {Dispatch} from "./Dispatch.sol"; @@ -25,17 +25,17 @@ import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; import {ErrorsLib as Errors} from "./lib/ErrorsLib.sol"; import {EventsLib as Events} from "./lib/EventsLib.sol"; -/// @title EulerAggregationLayer contract +/// @title EulerAggregationVault contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) /// @dev Do NOT use with fee on transfer tokens /// @dev Do NOT use with rebasing tokens /// @dev inspired by Yearn v3 ❤️ -contract EulerAggregationLayer is +contract EulerAggregationVault is ERC4626Upgradeable, AccessControlEnumerableUpgradeable, Dispatch, - IEulerAggregationLayer + IEulerAggregationVault { using SafeERC20 for IERC20; using SafeCast for uint256; @@ -57,7 +57,7 @@ contract EulerAggregationLayer is Dispatch(_rewardsModule, _hooksModule, _feeModule, _allocationPointsModule) {} - /// @notice Initialize the EulerAggregationLayer. + /// @notice Initialize the EulerAggregationVault. /// @param _initParams InitParams struct. function init(InitParams calldata _initParams) external initializer { __ERC4626_init_unchained(IERC20(_initParams.asset)); @@ -70,7 +70,7 @@ contract EulerAggregationLayer is $.locked = REENTRANCYLOCK__UNLOCKED; $.withdrawalQueue = _initParams.withdrawalQueuePlugin; $.rebalancer = _initParams.rebalancerPlugin; - $.strategies[address(0)] = IEulerAggregationLayer.Strategy({ + $.strategies[address(0)] = IEulerAggregationVault.Strategy({ allocated: 0, allocationPoints: _initParams.initialCashAllocationPoints.toUint120(), active: true, @@ -80,7 +80,7 @@ contract EulerAggregationLayer is $.balanceTracker = _initParams.balanceTracker; // Setup DEFAULT_ADMIN - _grantRole(DEFAULT_ADMIN_ROLE, _initParams.aggregationLayerOwner); + _grantRole(DEFAULT_ADMIN_ROLE, _initParams.aggregationVaultOwner); // Setup role admins _setRoleAdmin(ALLOCATIONS_MANAGER, ALLOCATIONS_MANAGER_ADMIN); @@ -209,7 +209,7 @@ contract EulerAggregationLayer is if (_msgSender() != $.rebalancer) revert Errors.NotRebalancer(); - IEulerAggregationLayer.Strategy memory strategyData = $.strategies[_strategy]; + IEulerAggregationVault.Strategy memory strategyData = $.strategies[_strategy]; if (_isDeposit) { // Do required approval (safely) and deposit @@ -289,7 +289,7 @@ contract EulerAggregationLayer is /// @notice Get strategy params. /// @param _strategy strategy's address /// @return Strategy struct - function getStrategy(address _strategy) external view returns (IEulerAggregationLayer.Strategy memory) { + function getStrategy(address _strategy) external view returns (IEulerAggregationVault.Strategy memory) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); return $.strategies[_strategy]; diff --git a/src/core/EulerAggregationLayerFactory.sol b/src/core/EulerAggregationVaultFactory.sol similarity index 84% rename from src/core/EulerAggregationLayerFactory.sol rename to src/core/EulerAggregationVaultFactory.sol index b32b454c..04b6c7b2 100644 --- a/src/core/EulerAggregationLayerFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -6,14 +6,14 @@ import {Rewards} from "./module/Rewards.sol"; import {Hooks} from "./module/Hooks.sol"; import {Fee} from "./module/Fee.sol"; import {WithdrawalQueue} from "../plugin/WithdrawalQueue.sol"; -import {EulerAggregationLayer, IEulerAggregationLayer} from "./EulerAggregationLayer.sol"; +import {EulerAggregationVault, IEulerAggregationVault} from "./EulerAggregationVault.sol"; // libs import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; -/// @title EulerAggregationLayerFactory contract +/// @title EulerAggregationVaultFactory contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) -contract EulerAggregationLayerFactory { +contract EulerAggregationVaultFactory { /// core dependencies address public immutable balanceTracker; /// core modules implementations addresses @@ -59,8 +59,8 @@ contract EulerAggregationLayerFactory { /// @param _name Vaut name. /// @param _symbol Vault symbol. /// @param _initialCashAllocationPoints The amount of points to initally allocate for cash reserve. - /// @return eulerAggregationLayer The address of the new deployed aggregation layer vault. - function deployEulerAggregationLayer( + /// @return eulerAggregationVault The address of the new deployed aggregation layer vault. + function deployEulerAggregationVault( address _asset, string memory _name, string memory _symbol, @@ -76,23 +76,23 @@ contract EulerAggregationLayerFactory { WithdrawalQueue withdrawalQueue = WithdrawalQueue(Clones.clone(withdrawalQueueImpl)); // deploy new aggregation vault - EulerAggregationLayer eulerAggregationLayer = - new EulerAggregationLayer(rewardsModuleAddr, hooksModuleAddr, feeModuleAddr, allocationpointsModuleAddr); + EulerAggregationVault eulerAggregationVault = + new EulerAggregationVault(rewardsModuleAddr, hooksModuleAddr, feeModuleAddr, allocationpointsModuleAddr); - IEulerAggregationLayer.InitParams memory aggregationVaultInitParams = IEulerAggregationLayer.InitParams({ + IEulerAggregationVault.InitParams memory aggregationVaultInitParams = IEulerAggregationVault.InitParams({ balanceTracker: balanceTracker, withdrawalQueuePlugin: address(withdrawalQueue), rebalancerPlugin: rebalancer, - aggregationLayerOwner: msg.sender, + aggregationVaultOwner: msg.sender, asset: _asset, name: _name, symbol: _symbol, initialCashAllocationPoints: _initialCashAllocationPoints }); - withdrawalQueue.init(msg.sender, address(eulerAggregationLayer)); - eulerAggregationLayer.init(aggregationVaultInitParams); + withdrawalQueue.init(msg.sender, address(eulerAggregationVault)); + eulerAggregationVault.init(aggregationVaultInitParams); - return address(eulerAggregationLayer); + return address(eulerAggregationVault); } } diff --git a/src/core/interface/IEulerAggregationLayer.sol b/src/core/interface/IEulerAggregationVault.sol similarity index 96% rename from src/core/interface/IEulerAggregationLayer.sol rename to src/core/interface/IEulerAggregationVault.sol index c0a165e8..6d71d4a9 100644 --- a/src/core/interface/IEulerAggregationLayer.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -interface IEulerAggregationLayer { +interface IEulerAggregationVault { /// @dev Struct to pass init() call params. struct InitParams { address balanceTracker; address withdrawalQueuePlugin; address rebalancerPlugin; - address aggregationLayerOwner; + address aggregationVaultOwner; address asset; string name; string symbol; diff --git a/src/core/lib/EventsLib.sol b/src/core/lib/EventsLib.sol index 7c4df926..43844c8f 100644 --- a/src/core/lib/EventsLib.sol +++ b/src/core/lib/EventsLib.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; library EventsLib { - /// @dev EulerAggregationLayer events + /// @dev EulerAggregationVault events event Gulp(uint256 interestLeft, uint256 interestSmearEnd); event AccruePerformanceFee(address indexed feeRecipient, uint256 yield, uint256 feeAssets); event Rebalance(address indexed strategy, uint256 amountToRebalance, bool isDeposit); diff --git a/src/core/lib/StorageLib.sol b/src/core/lib/StorageLib.sol index 21fcd2e2..19f8ed24 100644 --- a/src/core/lib/StorageLib.sol +++ b/src/core/lib/StorageLib.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.0; // interfaces -import {IEulerAggregationLayer} from "../interface/IEulerAggregationLayer.sol"; +import {IEulerAggregationVault} from "../interface/IEulerAggregationVault.sol"; /// @custom:storage-location erc7201:euler_aggregation_vault.storage.AggregationVault struct AggregationVaultStorage { - /// Total amount of _asset deposited into EulerAggregationLayer contract + /// Total amount of _asset deposited into EulerAggregationVault contract uint256 totalAssetsDeposited; /// Total amount of _asset deposited across all strategies. uint256 totalAllocated; @@ -21,7 +21,7 @@ struct AggregationVaultStorage { /// Rebalancer plugin address address rebalancer; /// Mapping between strategy address and it's allocation config - mapping(address => IEulerAggregationLayer.Strategy) strategies; + mapping(address => IEulerAggregationVault.Strategy) strategies; /// lastInterestUpdate: last timestamo where interest was updated. uint40 lastInterestUpdate; /// interestSmearEnd: timestamp when the smearing of interest end. diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index 4fd04de6..7ac8f4cc 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; // interfaces import {IERC4626} from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; import {IWithdrawalQueue} from "../interface/IWithdrawalQueue.sol"; -import {IEulerAggregationLayer} from "../interface/IEulerAggregationLayer.sol"; +import {IEulerAggregationVault} from "../interface/IEulerAggregationVault.sol"; // contracts import {Shared} from "../common/Shared.sol"; // libs @@ -27,7 +27,7 @@ abstract contract AllocationPointsModule is Shared { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); if (_newPoints == 0) revert Errors.InvalidAllocationPoints(); - IEulerAggregationLayer.Strategy memory strategyDataCache = $.strategies[_strategy]; + IEulerAggregationVault.Strategy memory strategyDataCache = $.strategies[_strategy]; if (!strategyDataCache.active) { revert Errors.InactiveStrategy(); @@ -74,7 +74,7 @@ abstract contract AllocationPointsModule is Shared { _callHooksTarget(ADD_STRATEGY, msg.sender); - $.strategies[_strategy] = IEulerAggregationLayer.Strategy({ + $.strategies[_strategy] = IEulerAggregationVault.Strategy({ allocated: 0, allocationPoints: _allocationPoints.toUint120(), active: true, @@ -96,7 +96,7 @@ abstract contract AllocationPointsModule is Shared { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - IEulerAggregationLayer.Strategy storage strategyStorage = $.strategies[_strategy]; + IEulerAggregationVault.Strategy storage strategyStorage = $.strategies[_strategy]; if (!strategyStorage.active) { revert Errors.AlreadyRemoved(); diff --git a/src/plugin/Rebalancer.sol b/src/plugin/Rebalancer.sol index cfef3d14..506639d8 100644 --- a/src/plugin/Rebalancer.sol +++ b/src/plugin/Rebalancer.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.0; // interfaces -import {IEulerAggregationLayer} from "../core/interface/IEulerAggregationLayer.sol"; +import {IEulerAggregationVault} from "../core/interface/IEulerAggregationVault.sol"; import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; /// @title Rebalancer plugin /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) -/// @notice A contract to execute rebalance() on the EulerAggregationLayer. +/// @notice A contract to execute rebalance() on the EulerAggregationVault. contract Rebalancer { event ExecuteRebalance( address indexed curatedVault, @@ -19,13 +19,13 @@ contract Rebalancer { ); /// @notice Rebalance strategies allocation for a specific curated vault. - /// @param _aggregationLayer Aggregation layer vault address. + /// @param _aggregationVault Aggregation layer vault address. /// @param _strategies Strategies addresses. - function executeRebalance(address _aggregationLayer, address[] calldata _strategies) external { - IEulerAggregationLayer(_aggregationLayer).gulp(); + function executeRebalance(address _aggregationVault, address[] calldata _strategies) external { + IEulerAggregationVault(_aggregationVault).gulp(); for (uint256 i; i < _strategies.length; ++i) { - _rebalance(_aggregationLayer, _strategies[i]); + _rebalance(_aggregationVault, _strategies[i]); } } @@ -33,18 +33,18 @@ contract Rebalancer { /// If current allocation is less than target allocation, the aggregator will: /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash /// - If all the available cash is greater than the max deposit, deposit the max deposit - /// @param _aggregationLayer Aggregation layer vault address. + /// @param _aggregationVault Aggregation layer vault address. /// @param _strategy Strategy address. - function _rebalance(address _aggregationLayer, address _strategy) private { + function _rebalance(address _aggregationVault, address _strategy) private { if (_strategy == address(0)) { return; //nothing to rebalance as that's the cash reserve } - IEulerAggregationLayer.Strategy memory strategyData = - IEulerAggregationLayer(_aggregationLayer).getStrategy(_strategy); + IEulerAggregationVault.Strategy memory strategyData = + IEulerAggregationVault(_aggregationVault).getStrategy(_strategy); - uint256 totalAllocationPointsCache = IEulerAggregationLayer(_aggregationLayer).totalAllocationPoints(); - uint256 totalAssetsAllocatableCache = IEulerAggregationLayer(_aggregationLayer).totalAssetsAllocatable(); + uint256 totalAllocationPointsCache = IEulerAggregationVault(_aggregationVault).totalAllocationPoints(); + uint256 totalAssetsAllocatableCache = IEulerAggregationVault(_aggregationVault).totalAssetsAllocatable(); uint256 targetAllocation = totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; @@ -56,7 +56,7 @@ contract Rebalancer { // Withdraw amountToRebalance = strategyData.allocated - targetAllocation; - uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(_aggregationLayer); + uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(_aggregationVault); if (amountToRebalance > maxWithdraw) { amountToRebalance = maxWithdraw; } @@ -65,10 +65,10 @@ contract Rebalancer { } else if (strategyData.allocated < targetAllocation) { // Deposit uint256 targetCash = totalAssetsAllocatableCache - * IEulerAggregationLayer(_aggregationLayer).getStrategy(address(0)).allocationPoints + * IEulerAggregationVault(_aggregationVault).getStrategy(address(0)).allocationPoints / totalAllocationPointsCache; uint256 currentCash = - totalAssetsAllocatableCache - IEulerAggregationLayer(_aggregationLayer).totalAllocated(); + totalAssetsAllocatableCache - IEulerAggregationVault(_aggregationVault).totalAllocated(); // Calculate available cash to put in strategies uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; @@ -78,7 +78,7 @@ contract Rebalancer { amountToRebalance = cashAvailable; } - uint256 maxDeposit = IERC4626(_strategy).maxDeposit(_aggregationLayer); + uint256 maxDeposit = IERC4626(_strategy).maxDeposit(_aggregationVault); if (amountToRebalance > maxDeposit) { amountToRebalance = maxDeposit; } @@ -90,8 +90,8 @@ contract Rebalancer { isDeposit = true; } - IEulerAggregationLayer(_aggregationLayer).rebalance(_strategy, amountToRebalance, isDeposit); + IEulerAggregationVault(_aggregationVault).rebalance(_strategy, amountToRebalance, isDeposit); - emit ExecuteRebalance(_aggregationLayer, _strategy, strategyData.allocated, targetAllocation, amountToRebalance); + emit ExecuteRebalance(_aggregationVault, _strategy, strategyData.allocated, targetAllocation, amountToRebalance); } } diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index 9c619ce9..efda5995 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; // interfaces import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; -import {IEulerAggregationLayer} from "../core/interface/IEulerAggregationLayer.sol"; +import {IEulerAggregationVault} from "../core/interface/IEulerAggregationVault.sol"; import {IWithdrawalQueue} from "../core/interface/IWithdrawalQueue.sol"; // contracts import {AccessControlEnumerableUpgradeable} from @@ -14,7 +14,7 @@ import {AccessControlEnumerableUpgradeable} from /// @author Euler Labs (https://www.eulerlabs.com/) /// @notice This contract manage the withdrawalQueue aray(add/remove strategy to the queue, re-order queue). /// Also it handles finishing the withdraw execution flow through the `callWithdrawalQueue()` function -/// that will be called by the EulerAggregationLayer. +/// that will be called by the EulerAggregationVault. contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue { error OutOfBounds(); error SameIndexes(); @@ -132,7 +132,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue uint256 desiredAssets = _assets - _availableAssets; uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; - IEulerAggregationLayer(eulerAggregationVaultCached).executeStrategyWithdraw( + IEulerAggregationVault(eulerAggregationVaultCached).executeStrategyWithdraw( address(strategy), withdrawAmount ); @@ -150,7 +150,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue revert NotEnoughAssets(); } - IEulerAggregationLayer(eulerAggregationVaultCached).executeAggregationVaultWithdraw( + IEulerAggregationVault(eulerAggregationVaultCached).executeAggregationVaultWithdraw( _caller, _receiver, _owner, _assets, _shares ); } diff --git a/test/A16zPropertyTests.t.sol b/test/A16zPropertyTests.t.sol index 298a7ace..1e487aaf 100644 --- a/test/A16zPropertyTests.t.sol +++ b/test/A16zPropertyTests.t.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.0; // a16z properties tests import {ERC4626Test} from "erc4626-tests/ERC4626.test.sol"; // contracts -import {EulerAggregationLayer} from "../src/core/EulerAggregationLayer.sol"; +import {EulerAggregationVault} from "../src/core/EulerAggregationVault.sol"; import {Rebalancer} from "../src/plugin/Rebalancer.sol"; import {Hooks} from "../src/core/module/Hooks.sol"; import {Rewards} from "../src/core/module/Rewards.sol"; import {Fee} from "../src/core/module/Fee.sol"; -import {EulerAggregationLayerFactory} from "../src/core/EulerAggregationLayerFactory.sol"; +import {EulerAggregationVaultFactory} from "../src/core/EulerAggregationVaultFactory.sol"; import {WithdrawalQueue} from "../src/plugin/WithdrawalQueue.sol"; import {AllocationPoints} from "../src/core/module/AllocationPoints.sol"; // mocks @@ -27,8 +27,8 @@ contract A16zPropertyTests is ERC4626Test { Rebalancer rebalancerPlugin; WithdrawalQueue withdrawalQueuePluginImpl; - EulerAggregationLayerFactory eulerAggregationLayerFactory; - EulerAggregationLayer eulerAggregationLayer; + EulerAggregationVaultFactory eulerAggregationVaultFactory; + EulerAggregationVault eulerAggregationVault; function setUp() public override { rewardsImpl = new Rewards(); @@ -39,7 +39,7 @@ contract A16zPropertyTests is ERC4626Test { rebalancerPlugin = new Rebalancer(); withdrawalQueuePluginImpl = new WithdrawalQueue(); - EulerAggregationLayerFactory.FactoryParams memory factoryParams = EulerAggregationLayerFactory.FactoryParams({ + EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ balanceTracker: address(0), rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), @@ -48,10 +48,10 @@ contract A16zPropertyTests is ERC4626Test { rebalancer: address(rebalancerPlugin), withdrawalQueueImpl: address(withdrawalQueuePluginImpl) }); - eulerAggregationLayerFactory = new EulerAggregationLayerFactory(factoryParams); + eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); _underlying_ = address(new ERC20Mock()); - _vault_ = eulerAggregationLayerFactory.deployEulerAggregationLayer( + _vault_ = eulerAggregationVaultFactory.deployEulerAggregationVault( _underlying_, "E20M_Agg", "E20M_Agg", CASH_RESERVE_ALLOCATION_POINTS ); _delta_ = 0; diff --git a/test/common/EulerAggregationLayerBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol similarity index 56% rename from test/common/EulerAggregationLayerBase.t.sol rename to test/common/EulerAggregationVaultBase.t.sol index 22245e06..46dc89c6 100644 --- a/test/common/EulerAggregationLayerBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -6,18 +6,18 @@ import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; import {IWithdrawalQueue} from "../../src/core/interface/IWithdrawalQueue.sol"; // contracts import "evk/test/unit/evault/EVaultTestBase.t.sol"; -import {EulerAggregationLayer, IEulerAggregationLayer} from "../../src/core/EulerAggregationLayer.sol"; +import {EulerAggregationVault, IEulerAggregationVault} from "../../src/core/EulerAggregationVault.sol"; import {Rebalancer} from "../../src/plugin/Rebalancer.sol"; import {Hooks, HooksModule} from "../../src/core/module/Hooks.sol"; import {Rewards} from "../../src/core/module/Rewards.sol"; import {Fee} from "../../src/core/module/Fee.sol"; -import {EulerAggregationLayerFactory} from "../../src/core/EulerAggregationLayerFactory.sol"; +import {EulerAggregationVaultFactory} from "../../src/core/EulerAggregationVaultFactory.sol"; import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; import {AllocationPoints} from "../../src/core/module/AllocationPoints.sol"; // libs import {ErrorsLib} from "../../src/core/lib/ErrorsLib.sol"; -contract EulerAggregationLayerBase is EVaultTestBase { +contract EulerAggregationVaultBase is EVaultTestBase { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; address deployer; @@ -34,8 +34,8 @@ contract EulerAggregationLayerBase is EVaultTestBase { Rebalancer rebalancer; WithdrawalQueue withdrawalQueueImpl; - EulerAggregationLayerFactory eulerAggregationLayerFactory; - EulerAggregationLayer eulerAggregationLayer; + EulerAggregationVaultFactory eulerAggregationVaultFactory; + EulerAggregationVault eulerAggregationVault; WithdrawalQueue withdrawalQueue; function setUp() public virtual override { @@ -55,7 +55,7 @@ contract EulerAggregationLayerBase is EVaultTestBase { rebalancer = new Rebalancer(); withdrawalQueueImpl = new WithdrawalQueue(); - EulerAggregationLayerFactory.FactoryParams memory factoryParams = EulerAggregationLayerFactory.FactoryParams({ + EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ balanceTracker: address(0), rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), @@ -64,84 +64,84 @@ contract EulerAggregationLayerBase is EVaultTestBase { rebalancer: address(rebalancer), withdrawalQueueImpl: address(withdrawalQueueImpl) }); - eulerAggregationLayerFactory = new EulerAggregationLayerFactory(factoryParams); + eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); - eulerAggregationLayer = EulerAggregationLayer( - eulerAggregationLayerFactory.deployEulerAggregationLayer( + eulerAggregationVault = EulerAggregationVault( + eulerAggregationVaultFactory.deployEulerAggregationVault( address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS ) ); - withdrawalQueue = WithdrawalQueue(eulerAggregationLayer.withdrawalQueue()); + withdrawalQueue = WithdrawalQueue(eulerAggregationVault.withdrawalQueue()); // grant admin roles to deployer - eulerAggregationLayer.grantRole(eulerAggregationLayer.ALLOCATIONS_MANAGER_ADMIN(), deployer); - eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_ADDER_ADMIN(), deployer); - eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_REMOVER_ADMIN(), deployer); - eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_ADDER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_REMOVER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_LAYER_MANAGER_ADMIN(), deployer); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager - eulerAggregationLayer.grantRole(eulerAggregationLayer.ALLOCATIONS_MANAGER(), manager); - eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_ADDER(), manager); - eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_REMOVER(), manager); - eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_ADDER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_REMOVER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_LAYER_MANAGER(), manager); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); vm.stopPrank(); - vm.label(address(eulerAggregationLayerFactory), "eulerAggregationLayerFactory"); - vm.label(address(eulerAggregationLayer), "eulerAggregationLayer"); - vm.label(eulerAggregationLayer.MODULE_REWARDS(), "MODULE_REWARDS"); - vm.label(eulerAggregationLayer.MODULE_HOOKS(), "MODULE_HOOKS"); - vm.label(eulerAggregationLayer.MODULE_FEE(), "MODULE_FEE"); - vm.label(eulerAggregationLayer.MODULE_ALLOCATION_POINTS(), "MODULE_ALLOCATION_POINTS"); + vm.label(address(eulerAggregationVaultFactory), "eulerAggregationVaultFactory"); + vm.label(address(eulerAggregationVault), "eulerAggregationVault"); + vm.label(eulerAggregationVault.MODULE_REWARDS(), "MODULE_REWARDS"); + vm.label(eulerAggregationVault.MODULE_HOOKS(), "MODULE_HOOKS"); + vm.label(eulerAggregationVault.MODULE_FEE(), "MODULE_FEE"); + vm.label(eulerAggregationVault.MODULE_ALLOCATION_POINTS(), "MODULE_ALLOCATION_POINTS"); vm.label(address(assetTST), "assetTST"); } function testInitialParams() public view { - EulerAggregationLayer.Strategy memory cashReserve = eulerAggregationLayer.getStrategy(address(0)); + EulerAggregationVault.Strategy memory cashReserve = eulerAggregationVault.getStrategy(address(0)); assertEq(cashReserve.allocated, 0); assertEq(cashReserve.allocationPoints, CASH_RESERVE_ALLOCATION_POINTS); assertEq(cashReserve.active, true); assertEq( - eulerAggregationLayer.getRoleAdmin(eulerAggregationLayer.ALLOCATIONS_MANAGER()), - eulerAggregationLayer.ALLOCATIONS_MANAGER_ADMIN() + eulerAggregationVault.getRoleAdmin(eulerAggregationVault.ALLOCATIONS_MANAGER()), + eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN() ); assertEq( - eulerAggregationLayer.getRoleAdmin(eulerAggregationLayer.STRATEGY_ADDER()), - eulerAggregationLayer.STRATEGY_ADDER_ADMIN() + eulerAggregationVault.getRoleAdmin(eulerAggregationVault.STRATEGY_ADDER()), + eulerAggregationVault.STRATEGY_ADDER_ADMIN() ); assertEq( - eulerAggregationLayer.getRoleAdmin(eulerAggregationLayer.STRATEGY_REMOVER()), - eulerAggregationLayer.STRATEGY_REMOVER_ADMIN() + eulerAggregationVault.getRoleAdmin(eulerAggregationVault.STRATEGY_REMOVER()), + eulerAggregationVault.STRATEGY_REMOVER_ADMIN() ); assertEq( - eulerAggregationLayer.getRoleAdmin(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER()), - eulerAggregationLayer.AGGREGATION_LAYER_MANAGER_ADMIN() + eulerAggregationVault.getRoleAdmin(eulerAggregationVault.AGGREGATION_LAYER_MANAGER()), + eulerAggregationVault.AGGREGATION_LAYER_MANAGER_ADMIN() ); assertEq( withdrawalQueue.getRoleAdmin(withdrawalQueue.WITHDRAW_QUEUE_MANAGER()), withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN() ); - assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.ALLOCATIONS_MANAGER_ADMIN(), deployer)); - assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.STRATEGY_ADDER_ADMIN(), deployer)); - assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.STRATEGY_REMOVER_ADMIN(), deployer)); - assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER_ADMIN(), deployer)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN(), deployer)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_ADDER_ADMIN(), deployer)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_REMOVER_ADMIN(), deployer)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_LAYER_MANAGER_ADMIN(), deployer)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); - assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.ALLOCATIONS_MANAGER(), manager)); - assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.STRATEGY_ADDER(), manager)); - assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.STRATEGY_REMOVER(), manager)); - assertTrue(eulerAggregationLayer.hasRole(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER(), manager)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.ALLOCATIONS_MANAGER(), manager)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_ADDER(), manager)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_REMOVER(), manager)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_LAYER_MANAGER(), manager)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); } function _addStrategy(address from, address strategy, uint256 allocationPoints) internal { vm.prank(from); - eulerAggregationLayer.addStrategy(strategy, allocationPoints); + eulerAggregationVault.addStrategy(strategy, allocationPoints); } function _getWithdrawalQueueLength() internal view returns (uint256) { diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 63bf1521..5c7f91e9 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -3,18 +3,18 @@ pragma solidity ^0.8.0; import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol"; import { - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, console2, EVault, IEVault, IRMTestDefault, TestERC20, - EulerAggregationLayerFactory, + EulerAggregationVaultFactory, Rewards -} from "../common/EulerAggregationLayerBase.t.sol"; +} from "../common/EulerAggregationVaultBase.t.sol"; -contract BalanceForwarderE2ETest is EulerAggregationLayerBase { +contract BalanceForwarderE2ETest is EulerAggregationVaultBase { uint256 user1InitialBalance = 100000e18; address trackingReward; @@ -25,7 +25,7 @@ contract BalanceForwarderE2ETest is EulerAggregationLayerBase { vm.startPrank(deployer); trackingReward = address(new TrackingRewardStreams(address(evc), 2 weeks)); - EulerAggregationLayerFactory.FactoryParams memory factoryParams = EulerAggregationLayerFactory.FactoryParams({ + EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ balanceTracker: trackingReward, rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), @@ -34,26 +34,26 @@ contract BalanceForwarderE2ETest is EulerAggregationLayerBase { rebalancer: address(rebalancer), withdrawalQueueImpl: address(withdrawalQueueImpl) }); - eulerAggregationLayerFactory = new EulerAggregationLayerFactory(factoryParams); - eulerAggregationLayer = EulerAggregationLayer( - eulerAggregationLayerFactory.deployEulerAggregationLayer( + eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); + eulerAggregationVault = EulerAggregationVault( + eulerAggregationVaultFactory.deployEulerAggregationVault( address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS ) ); // grant admin roles to deployer - eulerAggregationLayer.grantRole(eulerAggregationLayer.ALLOCATIONS_MANAGER_ADMIN(), deployer); - // eulerAggregationLayer.grantRole(eulerAggregationLayer.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); - eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_ADDER_ADMIN(), deployer); - eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_REMOVER_ADMIN(), deployer); - eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN(), deployer); + // eulerAggregationVault.grantRole(eulerAggregationVault.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_ADDER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_REMOVER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_LAYER_MANAGER_ADMIN(), deployer); // grant roles to manager - eulerAggregationLayer.grantRole(eulerAggregationLayer.ALLOCATIONS_MANAGER(), manager); - // eulerAggregationLayer.grantRole(eulerAggregationLayer.WITHDRAW_QUEUE_MANAGER(), manager); - eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_ADDER(), manager); - eulerAggregationLayer.grantRole(eulerAggregationLayer.STRATEGY_REMOVER(), manager); - eulerAggregationLayer.grantRole(eulerAggregationLayer.AGGREGATION_LAYER_MANAGER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER(), manager); + // eulerAggregationVault.grantRole(eulerAggregationVault.WITHDRAW_QUEUE_MANAGER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_ADDER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_REMOVER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_LAYER_MANAGER(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; @@ -63,125 +63,125 @@ contract BalanceForwarderE2ETest is EulerAggregationLayerBase { // deposit into aggregator uint256 amountToDeposit = 10000e18; { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } } function testBalanceForwarderrAddress_Integrity() public view { - assertEq(eulerAggregationLayer.balanceTrackerAddress(), trackingReward); + assertEq(eulerAggregationVault.balanceTrackerAddress(), trackingReward); } function testEnableBalanceForwarder() public { vm.prank(user1); - eulerAggregationLayer.enableBalanceForwarder(); + eulerAggregationVault.enableBalanceForwarder(); - assertTrue(eulerAggregationLayer.balanceForwarderEnabled(user1)); + assertTrue(eulerAggregationVault.balanceForwarderEnabled(user1)); assertEq( - TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationLayer)), - eulerAggregationLayer.balanceOf(user1) + TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationVault)), + eulerAggregationVault.balanceOf(user1) ); } function testDisableBalanceForwarder() public { vm.prank(user1); - eulerAggregationLayer.enableBalanceForwarder(); + eulerAggregationVault.enableBalanceForwarder(); - assertTrue(eulerAggregationLayer.balanceForwarderEnabled(user1)); + assertTrue(eulerAggregationVault.balanceForwarderEnabled(user1)); vm.prank(user1); - eulerAggregationLayer.disableBalanceForwarder(); + eulerAggregationVault.disableBalanceForwarder(); - assertFalse(eulerAggregationLayer.balanceForwarderEnabled(user1)); - assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationLayer)), 0); + assertFalse(eulerAggregationVault.balanceForwarderEnabled(user1)); + assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationVault)), 0); } function testHookWhenReceiverEnabled() public { vm.prank(user1); - eulerAggregationLayer.enableBalanceForwarder(); + eulerAggregationVault.enableBalanceForwarder(); // deposit into aggregator uint256 amountToDeposit = 10000e18; { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); assertEq( - TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationLayer)), - eulerAggregationLayer.balanceOf(user1) + TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationVault)), + eulerAggregationVault.balanceOf(user1) ); } } function testHookWhenSenderEnabled() public { vm.prank(user1); - eulerAggregationLayer.enableBalanceForwarder(); + eulerAggregationVault.enableBalanceForwarder(); // deposit into aggregator uint256 amountToDeposit = 10000e18; { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); assertEq( - TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationLayer)), - eulerAggregationLayer.balanceOf(user1) + TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationVault)), + eulerAggregationVault.balanceOf(user1) ); } { - uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 amountToWithdraw = eulerAggregationVault.balanceOf(user1); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); + eulerAggregationVault.redeem(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(eulerAggregationLayer)), 0); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(eulerAggregationVault)), 0); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(eulerAggregationVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq( assetTST.balanceOf(user1), - user1AssetTSTBalanceBefore + eulerAggregationLayer.convertToAssets(amountToWithdraw) + user1AssetTSTBalanceBefore + eulerAggregationVault.convertToAssets(amountToWithdraw) ); - assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationLayer)), 0); + assertEq(TrackingRewardStreams(trackingReward).balanceOf(user1, address(eulerAggregationVault)), 0); } } } diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 15fa6670..d5646545 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, console2, EVault, IEVault, IRMTestDefault, TestERC20, WithdrawalQueue, - IEulerAggregationLayer -} from "../common/EulerAggregationLayerBase.t.sol"; + IEulerAggregationVault +} from "../common/EulerAggregationVaultBase.t.sol"; -contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { +contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -30,41 +30,41 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); + assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); assertEq( - (eulerAggregationLayer.getStrategy(address(eTST))).allocated, + (eulerAggregationVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash ); } @@ -73,37 +73,37 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { // partial withdraw, no need to withdraw from strategy as cash reserve is enough uint256 amountToWithdraw = 6000e18; { - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - uint256 strategyShareBalanceBefore = eTST.balanceOf(address(eulerAggregationLayer)); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); + uint256 strategyShareBalanceBefore = eTST.balanceOf(address(eulerAggregationVault)); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - eulerAggregationLayer.withdraw(amountToWithdraw, user1, user1); + eulerAggregationVault.withdraw(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(eulerAggregationLayer)), strategyShareBalanceBefore); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(eulerAggregationVault)), strategyShareBalanceBefore); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(eulerAggregationVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated); } // full withdraw, will have to withdraw from strategy as cash reserve is not enough { amountToWithdraw = amountToDeposit - amountToWithdraw; - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - eulerAggregationLayer.withdraw(amountToWithdraw, user1, user1); + eulerAggregationVault.withdraw(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(eulerAggregationLayer)), 0); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(eulerAggregationVault)), 0); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(eulerAggregationVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToWithdraw); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, 0); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, 0); } } @@ -112,67 +112,67 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); } vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(eulerAggregationLayer)); + eTST.skim(type(uint256).max, address(eulerAggregationVault)); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 amountToWithdraw = eulerAggregationVault.balanceOf(user1); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); + eulerAggregationVault.redeem(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(eulerAggregationLayer)), yield); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(eulerAggregationVault)), yield); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(eulerAggregationVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq( assetTST.balanceOf(user1), - user1AssetTSTBalanceBefore + eulerAggregationLayer.convertToAssets(amountToWithdraw) + user1AssetTSTBalanceBefore + eulerAggregationVault.convertToAssets(amountToWithdraw) ); } } @@ -197,19 +197,19 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -218,20 +218,20 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - IEulerAggregationLayer.Strategy memory eTSTstrategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - IEulerAggregationLayer.Strategy memory eTSTsecondarystrategyBefore = - eulerAggregationLayer.getStrategy(address(eTSTsecondary)); + IEulerAggregationVault.Strategy memory eTSTstrategyBefore = eulerAggregationVault.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory eTSTsecondarystrategyBefore = + eulerAggregationVault.getStrategy(address(eTSTsecondary)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), eTSTstrategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), eTSTstrategyBefore.allocated); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), eTSTsecondarystrategyBefore.allocated ); - uint256 expectedeTSTStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * eTSTstrategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); - uint256 expectedeTSTsecondaryStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * eTSTsecondarystrategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedeTSTStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); assertTrue(expectedeTSTStrategyCash != 0); assertTrue(expectedeTSTsecondaryStrategyCash != 0); @@ -240,31 +240,31 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); assertEq( - eulerAggregationLayer.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash + eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash ); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedeTSTStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedeTSTStrategyCash); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), expectedeTSTsecondaryStrategyCash ); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); assertEq( - (eulerAggregationLayer.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + (eulerAggregationVault.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash ); assertEq( - assetTST.balanceOf(address(eulerAggregationLayer)), + assetTST.balanceOf(address(eulerAggregationVault)), amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) ); } vm.warp(block.timestamp + 86400); // mock an increase of aggregator balance due to yield - uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); - uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance); uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; uint256 aggrNeweTSTsecondaryUnderlyingBalance = aggrCurrenteTSTsecondaryUnderlyingBalance * 11e17 / 1e18; @@ -273,25 +273,25 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { assetTST.mint(address(eTST), eTSTYield); assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield); - eTST.skim(type(uint256).max, address(eulerAggregationLayer)); - eTSTsecondary.skim(type(uint256).max, address(eulerAggregationLayer)); + eTST.skim(type(uint256).max, address(eulerAggregationVault)); + eTSTsecondary.skim(type(uint256).max, address(eulerAggregationVault)); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 amountToWithdraw = eulerAggregationVault.balanceOf(user1); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); + eulerAggregationVault.redeem(amountToWithdraw, user1, user1); - assertEq(eTST.balanceOf(address(eulerAggregationLayer)), 0); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eTST.balanceOf(address(eulerAggregationVault)), 0); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + assertEq(eulerAggregationVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertEq( assetTST.balanceOf(user1), - user1AssetTSTBalanceBefore + eulerAggregationLayer.convertToAssets(amountToWithdraw) + user1AssetTSTBalanceBefore + eulerAggregationVault.convertToAssets(amountToWithdraw) ); } } @@ -301,72 +301,72 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); } vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; uint256 yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(eulerAggregationLayer)); + eTST.skim(type(uint256).max, address(eulerAggregationVault)); // harvest vm.prank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); vm.warp(block.timestamp + 2 weeks); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 amountToWithdraw = eulerAggregationVault.balanceOf(user1); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); + eulerAggregationVault.redeem(amountToWithdraw, user1, user1); // all yield is distributed - assertApproxEqAbs(eTST.balanceOf(address(eulerAggregationLayer)), 0, 1); + assertApproxEqAbs(eTST.balanceOf(address(eulerAggregationVault)), 0, 1); assertApproxEqAbs( - eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1 + eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1 ); - assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eulerAggregationVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToDeposit + yield, 1); } } @@ -391,19 +391,19 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -412,20 +412,20 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - IEulerAggregationLayer.Strategy memory eTSTstrategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - IEulerAggregationLayer.Strategy memory eTSTsecondarystrategyBefore = - eulerAggregationLayer.getStrategy(address(eTSTsecondary)); + IEulerAggregationVault.Strategy memory eTSTstrategyBefore = eulerAggregationVault.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory eTSTsecondarystrategyBefore = + eulerAggregationVault.getStrategy(address(eTSTsecondary)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), eTSTstrategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), eTSTstrategyBefore.allocated); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), eTSTsecondarystrategyBefore.allocated ); - uint256 expectedeTSTStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * eTSTstrategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); - uint256 expectedeTSTsecondaryStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * eTSTsecondarystrategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedeTSTStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); assertTrue(expectedeTSTStrategyCash != 0); assertTrue(expectedeTSTsecondaryStrategyCash != 0); @@ -434,22 +434,22 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); assertEq( - eulerAggregationLayer.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash + eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash ); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedeTSTStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedeTSTStrategyCash); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), expectedeTSTsecondaryStrategyCash ); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); assertEq( - (eulerAggregationLayer.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + (eulerAggregationVault.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash ); assertEq( - assetTST.balanceOf(address(eulerAggregationLayer)), + assetTST.balanceOf(address(eulerAggregationVault)), amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) ); } @@ -459,9 +459,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { uint256 eTSTsecondaryYield; { // mock an increase of aggregator balance due to yield - uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); - uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance); uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; @@ -471,30 +471,30 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { assetTST.mint(address(eTST), eTSTYield); assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield); - eTST.skim(type(uint256).max, address(eulerAggregationLayer)); - eTSTsecondary.skim(type(uint256).max, address(eulerAggregationLayer)); + eTST.skim(type(uint256).max, address(eulerAggregationVault)); + eTSTsecondary.skim(type(uint256).max, address(eulerAggregationVault)); } // harvest vm.prank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); vm.warp(block.timestamp + 2 weeks); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 amountToWithdraw = eulerAggregationVault.balanceOf(user1); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.prank(user1); - eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); + eulerAggregationVault.redeem(amountToWithdraw, user1, user1); - assertApproxEqAbs(eTST.balanceOf(address(eulerAggregationLayer)), 0, 0); + assertApproxEqAbs(eTST.balanceOf(address(eulerAggregationVault)), 0, 0); assertApproxEqAbs( - eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1 + eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw, 1 ); - assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eulerAggregationVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertApproxEqAbs( assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + amountToDeposit + eTSTYield + eTSTsecondaryYield, @@ -523,19 +523,19 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -544,20 +544,20 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { - IEulerAggregationLayer.Strategy memory eTSTstrategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - IEulerAggregationLayer.Strategy memory eTSTsecondarystrategyBefore = - eulerAggregationLayer.getStrategy(address(eTSTsecondary)); + IEulerAggregationVault.Strategy memory eTSTstrategyBefore = eulerAggregationVault.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory eTSTsecondarystrategyBefore = + eulerAggregationVault.getStrategy(address(eTSTsecondary)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), eTSTstrategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), eTSTstrategyBefore.allocated); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), eTSTsecondarystrategyBefore.allocated ); - uint256 expectedeTSTStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * eTSTstrategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); - uint256 expectedeTSTsecondaryStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * eTSTsecondarystrategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedeTSTStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); assertTrue(expectedeTSTStrategyCash != 0); assertTrue(expectedeTSTsecondaryStrategyCash != 0); @@ -566,22 +566,22 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); assertEq( - eulerAggregationLayer.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash + eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash ); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedeTSTStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedeTSTStrategyCash); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), expectedeTSTsecondaryStrategyCash ); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); assertEq( - (eulerAggregationLayer.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + (eulerAggregationVault.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash ); assertEq( - assetTST.balanceOf(address(eulerAggregationLayer)), + assetTST.balanceOf(address(eulerAggregationVault)), amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) ); } @@ -591,9 +591,9 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { uint256 eTSTsecondaryYield; { // mock an increase of aggregator balance due to yield - uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); - uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance); uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; @@ -603,34 +603,34 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationLayerBase { assetTST.mint(address(eTST), eTSTYield); assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield); - eTST.skim(type(uint256).max, address(eulerAggregationLayer)); - eTSTsecondary.skim(type(uint256).max, address(eulerAggregationLayer)); + eTST.skim(type(uint256).max, address(eulerAggregationVault)); + eTSTsecondary.skim(type(uint256).max, address(eulerAggregationVault)); } // harvest vm.prank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); vm.warp(block.timestamp + 2 weeks); // vm.prank(manager); - // eulerAggregationLayer.removeStrategy(address(eTSTsecondary)); + // eulerAggregationVault.removeStrategy(address(eTSTsecondary)); // vm.mockCall( // address(eTST), - // abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), + // abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationVault)), // abi.encode(0) // ); // vm.mockCall( // address(eTSTsecondary), - // abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), + // abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationVault)), // abi.encode(0) // ); { - uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); + uint256 amountToWithdraw = eulerAggregationVault.balanceOf(user1); vm.prank(user1); // vm.expectRevert(WithdrawalQueue.NotEnoughAssets.selector); - eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); + eulerAggregationVault.redeem(amountToWithdraw, user1, user1); } // vm.clearMockedCalls(); } diff --git a/test/e2e/HarvestRedeemE2ETest.t.sol b/test/e2e/HarvestRedeemE2ETest.t.sol index 54634721..87c6d1ba 100644 --- a/test/e2e/HarvestRedeemE2ETest.t.sol +++ b/test/e2e/HarvestRedeemE2ETest.t.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, console2, EVault, - IEulerAggregationLayer -} from "../common/EulerAggregationLayerBase.t.sol"; + IEulerAggregationVault +} from "../common/EulerAggregationVaultBase.t.sol"; -contract HarvestRedeemE2ETest is EulerAggregationLayerBase { +contract HarvestRedeemE2ETest is EulerAggregationVaultBase { uint256 user1InitialBalance = 100000e18; uint256 amountToDeposit = 10000e18; @@ -23,40 +23,40 @@ contract HarvestRedeemE2ETest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); } } @@ -64,36 +64,36 @@ contract HarvestRedeemE2ETest is EulerAggregationLayerBase { vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationVault)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationVault)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); assertTrue(expectedAllocated < strategyBefore.allocated); - uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(eulerAggregationLayer)); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(eulerAggregationVault)); - uint256 user1SharesBefore = eulerAggregationLayer.balanceOf(user1); - uint256 user1SocializedLoss = user1SharesBefore * negativeYield / eulerAggregationLayer.totalSupply(); + uint256 user1SharesBefore = eulerAggregationVault.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / eulerAggregationVault.totalSupply(); uint256 expectedUser1Assets = - user1SharesBefore * amountToDeposit / eulerAggregationLayer.totalSupply() - user1SocializedLoss; + user1SharesBefore * amountToDeposit / eulerAggregationVault.totalSupply() - user1SocializedLoss; uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - eulerAggregationLayer.harvest(); - eulerAggregationLayer.redeem(user1SharesBefore, user1, user1); + eulerAggregationVault.harvest(); + eulerAggregationVault.redeem(user1SharesBefore, user1, user1); vm.stopPrank(); - uint256 user1SharesAfter = eulerAggregationLayer.balanceOf(user1); + uint256 user1SharesAfter = eulerAggregationVault.balanceOf(user1); assertEq(user1SharesAfter, 0); assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedUser1Assets, 1); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), 0); + assertEq(eulerAggregationVault.totalAssetsDeposited(), 0); } function testHarvestNegativeYieldAndRedeemMultipleUser() public { @@ -102,55 +102,55 @@ contract HarvestRedeemE2ETest is EulerAggregationLayerBase { // deposit into aggregator { vm.startPrank(user2); - assetTST.approve(address(eulerAggregationLayer), user2InitialBalance); - eulerAggregationLayer.deposit(user2InitialBalance, user2); + assetTST.approve(address(eulerAggregationVault), user2InitialBalance); + eulerAggregationVault.deposit(user2InitialBalance, user2); vm.stopPrank(); } vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationVault)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationVault)); assertTrue(expectedAllocated < strategyBefore.allocated); - uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(eulerAggregationLayer)); - uint256 user1SharesBefore = eulerAggregationLayer.balanceOf(user1); - uint256 user1SocializedLoss = user1SharesBefore * negativeYield / eulerAggregationLayer.totalSupply(); - uint256 user2SharesBefore = eulerAggregationLayer.balanceOf(user2); - uint256 user2SocializedLoss = user2SharesBefore * negativeYield / eulerAggregationLayer.totalSupply(); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(eulerAggregationVault)); + uint256 user1SharesBefore = eulerAggregationVault.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / eulerAggregationVault.totalSupply(); + uint256 user2SharesBefore = eulerAggregationVault.balanceOf(user2); + uint256 user2SocializedLoss = user2SharesBefore * negativeYield / eulerAggregationVault.totalSupply(); uint256 expectedUser1Assets = user1SharesBefore * (amountToDeposit + user2InitialBalance) - / eulerAggregationLayer.totalSupply() - user1SocializedLoss; + / eulerAggregationVault.totalSupply() - user1SocializedLoss; uint256 expectedUser2Assets = user2SharesBefore * (amountToDeposit + user2InitialBalance) - / eulerAggregationLayer.totalSupply() - user2SocializedLoss; + / eulerAggregationVault.totalSupply() - user2SocializedLoss; uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); uint256 user2AssetTSTBalanceBefore = assetTST.balanceOf(user2); vm.startPrank(user1); - eulerAggregationLayer.harvest(); - eulerAggregationLayer.redeem(user1SharesBefore, user1, user1); + eulerAggregationVault.harvest(); + eulerAggregationVault.redeem(user1SharesBefore, user1, user1); vm.stopPrank(); vm.prank(user2); - eulerAggregationLayer.redeem(user2SharesBefore, user2, user2); + eulerAggregationVault.redeem(user2SharesBefore, user2, user2); - uint256 user1SharesAfter = eulerAggregationLayer.balanceOf(user1); - uint256 user2SharesAfter = eulerAggregationLayer.balanceOf(user2); + uint256 user1SharesAfter = eulerAggregationVault.balanceOf(user1); + uint256 user2SharesAfter = eulerAggregationVault.balanceOf(user2); assertEq(user1SharesAfter, 0); assertEq(user2SharesAfter, 0); assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedUser1Assets, 1); assertApproxEqAbs(assetTST.balanceOf(user2), user2AssetTSTBalanceBefore + expectedUser2Assets, 1); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), 0); + assertEq(eulerAggregationVault.totalAssetsDeposited(), 0); } } diff --git a/test/e2e/HooksE2ETest.t.sol b/test/e2e/HooksE2ETest.t.sol index 37709f27..bc8f0b95 100644 --- a/test/e2e/HooksE2ETest.t.sol +++ b/test/e2e/HooksE2ETest.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, console2, EVault, IEVault, @@ -11,9 +11,9 @@ import { TestERC20, IHookTarget, ErrorsLib -} from "../common/EulerAggregationLayerBase.t.sol"; +} from "../common/EulerAggregationVaultBase.t.sol"; -contract HooksE2ETest is EulerAggregationLayerBase { +contract HooksE2ETest is EulerAggregationVaultBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -26,38 +26,38 @@ contract HooksE2ETest is EulerAggregationLayerBase { } function testSetHooksConfig() public { - uint32 expectedHookedFns = eulerAggregationLayer.DEPOSIT() | eulerAggregationLayer.WITHDRAW() - | eulerAggregationLayer.ADD_STRATEGY() | eulerAggregationLayer.REMOVE_STRATEGY(); + uint32 expectedHookedFns = eulerAggregationVault.DEPOSIT() | eulerAggregationVault.WITHDRAW() + | eulerAggregationVault.ADD_STRATEGY() | eulerAggregationVault.REMOVE_STRATEGY(); vm.startPrank(manager); address hooksContract = address(new HooksContract()); - eulerAggregationLayer.setHooksConfig(hooksContract, expectedHookedFns); + eulerAggregationVault.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); - (address hookTarget, uint32 hookedFns) = eulerAggregationLayer.getHooksConfig(); + (address hookTarget, uint32 hookedFns) = eulerAggregationVault.getHooksConfig(); assertEq(hookTarget, hooksContract); assertEq(hookedFns, expectedHookedFns); } function testSetHooksConfigWithAddressZero() public { - uint32 expectedHookedFns = eulerAggregationLayer.DEPOSIT() | eulerAggregationLayer.WITHDRAW() - | eulerAggregationLayer.ADD_STRATEGY() | eulerAggregationLayer.REMOVE_STRATEGY(); + uint32 expectedHookedFns = eulerAggregationVault.DEPOSIT() | eulerAggregationVault.WITHDRAW() + | eulerAggregationVault.ADD_STRATEGY() | eulerAggregationVault.REMOVE_STRATEGY(); vm.startPrank(manager); vm.expectRevert(ErrorsLib.InvalidHooksTarget.selector); - eulerAggregationLayer.setHooksConfig(address(0), expectedHookedFns); + eulerAggregationVault.setHooksConfig(address(0), expectedHookedFns); vm.stopPrank(); } function testSetHooksConfigWithNotHooksContract() public { - uint32 expectedHookedFns = eulerAggregationLayer.DEPOSIT() | eulerAggregationLayer.WITHDRAW() - | eulerAggregationLayer.ADD_STRATEGY() | eulerAggregationLayer.REMOVE_STRATEGY(); + uint32 expectedHookedFns = eulerAggregationVault.DEPOSIT() | eulerAggregationVault.WITHDRAW() + | eulerAggregationVault.ADD_STRATEGY() | eulerAggregationVault.REMOVE_STRATEGY(); vm.startPrank(manager); address hooksContract = address(new NotHooksContract()); vm.expectRevert(ErrorsLib.NotHooksContract.selector); - eulerAggregationLayer.setHooksConfig(hooksContract, expectedHookedFns); + eulerAggregationVault.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); } @@ -66,33 +66,33 @@ contract HooksE2ETest is EulerAggregationLayerBase { vm.startPrank(manager); address hooksContract = address(new HooksContract()); vm.expectRevert(ErrorsLib.InvalidHookedFns.selector); - eulerAggregationLayer.setHooksConfig(hooksContract, expectedHookedFns); + eulerAggregationVault.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); } function testHookedDeposit() public { - uint32 expectedHookedFns = eulerAggregationLayer.DEPOSIT(); + uint32 expectedHookedFns = eulerAggregationVault.DEPOSIT(); vm.startPrank(manager); address hooksContract = address(new HooksContract()); - eulerAggregationLayer.setHooksConfig(hooksContract, expectedHookedFns); + eulerAggregationVault.setHooksConfig(hooksContract, expectedHookedFns); vm.stopPrank(); uint256 amountToDeposit = 10000e18; // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } } diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 731c1c96..4b2e9883 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -2,17 +2,17 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, console2, EVault, IEVault, IRMTestDefault, TestERC20, - IEulerAggregationLayer -} from "../common/EulerAggregationLayerBase.t.sol"; + IEulerAggregationVault +} from "../common/EulerAggregationVaultBase.t.sol"; -contract PerformanceFeeE2ETest is EulerAggregationLayerBase { +contract PerformanceFeeE2ETest is EulerAggregationVaultBase { uint256 user1InitialBalance = 100000e18; address feeRecipient; @@ -29,18 +29,18 @@ contract PerformanceFeeE2ETest is EulerAggregationLayerBase { } function testSetPerformanceFee() public { - (address feeRecipientAddr, uint256 fee) = eulerAggregationLayer.performanceFeeConfig(); + (address feeRecipientAddr, uint256 fee) = eulerAggregationVault.performanceFeeConfig(); assertEq(fee, 0); assertEq(feeRecipientAddr, address(0)); uint256 newPerformanceFee = 3e17; vm.startPrank(manager); - eulerAggregationLayer.setFeeRecipient(feeRecipient); - eulerAggregationLayer.setPerformanceFee(newPerformanceFee); + eulerAggregationVault.setFeeRecipient(feeRecipient); + eulerAggregationVault.setPerformanceFee(newPerformanceFee); vm.stopPrank(); - (feeRecipientAddr, fee) = eulerAggregationLayer.performanceFeeConfig(); + (feeRecipientAddr, fee) = eulerAggregationVault.performanceFeeConfig(); assertEq(fee, newPerformanceFee); assertEq(feeRecipientAddr, feeRecipient); } @@ -49,111 +49,111 @@ contract PerformanceFeeE2ETest is EulerAggregationLayerBase { uint256 newPerformanceFee = 3e17; vm.startPrank(manager); - eulerAggregationLayer.setFeeRecipient(feeRecipient); - eulerAggregationLayer.setPerformanceFee(newPerformanceFee); + eulerAggregationVault.setFeeRecipient(feeRecipient); + eulerAggregationVault.setPerformanceFee(newPerformanceFee); vm.stopPrank(); uint256 amountToDeposit = 10000e18; // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); } vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% uint256 yield; { - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(eulerAggregationLayer)); + eTST.skim(type(uint256).max, address(eulerAggregationVault)); } - (, uint256 performanceFee) = eulerAggregationLayer.performanceFeeConfig(); + (, uint256 performanceFee) = eulerAggregationVault.performanceFeeConfig(); uint256 expectedPerformanceFee = yield * performanceFee / 1e18; - IEulerAggregationLayer.Strategy memory strategyBeforeHarvest = eulerAggregationLayer.getStrategy(address(eTST)); - uint256 totalAllocatedBefore = eulerAggregationLayer.totalAllocated(); + IEulerAggregationVault.Strategy memory strategyBeforeHarvest = eulerAggregationVault.getStrategy(address(eTST)); + uint256 totalAllocatedBefore = eulerAggregationVault.totalAllocated(); // harvest vm.prank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); assertEq(assetTST.balanceOf(feeRecipient), expectedPerformanceFee); assertEq( - eulerAggregationLayer.getStrategy(address(eTST)).allocated, + eulerAggregationVault.getStrategy(address(eTST)).allocated, strategyBeforeHarvest.allocated + yield - expectedPerformanceFee ); - assertEq(eulerAggregationLayer.totalAllocated(), totalAllocatedBefore + yield - expectedPerformanceFee); + assertEq(eulerAggregationVault.totalAllocated(), totalAllocatedBefore + yield - expectedPerformanceFee); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { - uint256 amountToWithdraw = eulerAggregationLayer.balanceOf(user1); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = eulerAggregationLayer.totalSupply(); + uint256 amountToWithdraw = eulerAggregationVault.balanceOf(user1); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); - uint256 expectedAssetTST = eulerAggregationLayer.convertToAssets(eulerAggregationLayer.balanceOf(user1)); + uint256 expectedAssetTST = eulerAggregationVault.convertToAssets(eulerAggregationVault.balanceOf(user1)); vm.prank(user1); - eulerAggregationLayer.redeem(amountToWithdraw, user1, user1); + eulerAggregationVault.redeem(amountToWithdraw, user1, user1); assertApproxEqAbs( - eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssetTST, 1 + eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssetTST, 1 ); - assertEq(eulerAggregationLayer.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + assertEq(eulerAggregationVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedAssetTST, 1); } // full withdraw of recipient fees { - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 assetTSTBalanceBefore = assetTST.balanceOf(feeRecipient); - uint256 feeShares = eulerAggregationLayer.balanceOf(feeRecipient); - uint256 expectedAssets = eulerAggregationLayer.convertToAssets(feeShares); + uint256 feeShares = eulerAggregationVault.balanceOf(feeRecipient); + uint256 expectedAssets = eulerAggregationVault.convertToAssets(feeShares); vm.prank(feeRecipient); - eulerAggregationLayer.redeem(feeShares, feeRecipient, feeRecipient); + eulerAggregationVault.redeem(feeShares, feeRecipient, feeRecipient); assertApproxEqAbs( - eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssets, 1 + eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssets, 1 ); - assertEq(eulerAggregationLayer.totalSupply(), 0); + assertEq(eulerAggregationVault.totalSupply(), 0); assertApproxEqAbs(assetTST.balanceOf(feeRecipient), assetTSTBalanceBefore + expectedAssets, 1); } } diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index d2d4a4c5..01cc47c3 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, console2, EVault, IEVault, IRMTestDefault, TestERC20, - IEulerAggregationLayer, + IEulerAggregationVault, ErrorsLib -} from "../common/EulerAggregationLayerBase.t.sol"; +} from "../common/EulerAggregationVaultBase.t.sol"; -contract StrategyCapE2ETest is EulerAggregationLayerBase { +contract StrategyCapE2ETest is EulerAggregationVaultBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -28,12 +28,12 @@ contract StrategyCapE2ETest is EulerAggregationLayerBase { function testSetCap() public { uint256 cap = 1000000e18; - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).cap, 0); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).cap, 0); vm.prank(manager); - eulerAggregationLayer.setStrategyCap(address(eTST), cap); + eulerAggregationVault.setStrategyCap(address(eTST), cap); - IEulerAggregationLayer.Strategy memory strategy = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategy = eulerAggregationVault.getStrategy(address(eTST)); assertEq(strategy.cap, cap); } @@ -43,7 +43,7 @@ contract StrategyCapE2ETest is EulerAggregationLayerBase { vm.prank(manager); vm.expectRevert(ErrorsLib.InactiveStrategy.selector); - eulerAggregationLayer.setStrategyCap(address(0x2), cap); + eulerAggregationVault.setStrategyCap(address(0x2), cap); } function testRebalanceAfterHittingCap() public { @@ -51,46 +51,46 @@ contract StrategyCapE2ETest is EulerAggregationLayerBase { uint256 cap = 3333333333333333333333; vm.prank(manager); - eulerAggregationLayer.setStrategyCap(address(eTST), cap); + eulerAggregationVault.setStrategyCap(address(eTST), cap); uint256 amountToDeposit = 10000e18; // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); vm.prank(user1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); + assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); assertEq( - (eulerAggregationLayer.getStrategy(address(eTST))).allocated, + (eulerAggregationVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash ); } @@ -99,16 +99,16 @@ contract StrategyCapE2ETest is EulerAggregationLayerBase { vm.warp(block.timestamp + 86400); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); - uint256 strategyAllocatedBefore = (eulerAggregationLayer.getStrategy(address(eTST))).allocated; + uint256 strategyAllocatedBefore = (eulerAggregationVault.getStrategy(address(eTST))).allocated; strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); vm.stopPrank(); - assertEq(strategyAllocatedBefore, (eulerAggregationLayer.getStrategy(address(eTST))).allocated); + assertEq(strategyAllocatedBefore, (eulerAggregationVault.getStrategy(address(eTST))).allocated); } function testRebalanceWhentargetAllocationGreaterThanCap() public { @@ -116,45 +116,45 @@ contract StrategyCapE2ETest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); // set cap 10% less than target allocation uint256 cap = expectedStrategyCash * 9e17 / 1e18; vm.prank(manager); - eulerAggregationLayer.setStrategyCap(address(eTST), cap); + eulerAggregationVault.setStrategyCap(address(eTST), cap); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - assertEq(eulerAggregationLayer.totalAllocated(), cap); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), cap); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated + cap); + assertEq(eulerAggregationVault.totalAllocated(), cap); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), cap); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated + cap); } } } diff --git a/test/e2e/StrategyRewardsE2ETest.t.sol b/test/e2e/StrategyRewardsE2ETest.t.sol index d43b89b1..9ec6d018 100644 --- a/test/e2e/StrategyRewardsE2ETest.t.sol +++ b/test/e2e/StrategyRewardsE2ETest.t.sol @@ -2,17 +2,17 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, console2, EVault, IEVault, IRMTestDefault, TestERC20 -} from "../common/EulerAggregationLayerBase.t.sol"; +} from "../common/EulerAggregationVaultBase.t.sol"; import {TrackingRewardStreams} from "reward-streams/TrackingRewardStreams.sol"; -contract StrategyRewardsE2ETest is EulerAggregationLayerBase { +contract StrategyRewardsE2ETest is EulerAggregationVaultBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -26,19 +26,19 @@ contract StrategyRewardsE2ETest is EulerAggregationLayerBase { function testOptInStrategyRewards() public { vm.prank(manager); - eulerAggregationLayer.optInStrategyRewards(address(eTST)); + eulerAggregationVault.optInStrategyRewards(address(eTST)); - assertTrue(eTST.balanceForwarderEnabled(address(eulerAggregationLayer))); + assertTrue(eTST.balanceForwarderEnabled(address(eulerAggregationVault))); } function testOptOutStrategyRewards() public { vm.prank(manager); - eulerAggregationLayer.optInStrategyRewards(address(eTST)); - assertTrue(eTST.balanceForwarderEnabled(address(eulerAggregationLayer))); + eulerAggregationVault.optInStrategyRewards(address(eTST)); + assertTrue(eTST.balanceForwarderEnabled(address(eulerAggregationVault))); vm.prank(manager); - eulerAggregationLayer.optOutStrategyRewards(address(eTST)); + eulerAggregationVault.optOutStrategyRewards(address(eTST)); - assertFalse(eTST.balanceForwarderEnabled(address(eulerAggregationLayer))); + assertFalse(eTST.balanceForwarderEnabled(address(eulerAggregationVault))); } } diff --git a/test/echidna/CryticERC4626TestsHarness.t.sol b/test/echidna/CryticERC4626TestsHarness.t.sol index 572eec44..62b46891 100644 --- a/test/echidna/CryticERC4626TestsHarness.t.sol +++ b/test/echidna/CryticERC4626TestsHarness.t.sol @@ -4,12 +4,12 @@ pragma solidity ^0.8.0; // echidna erc-4626 properties tests import {CryticERC4626PropertyTests} from "crytic-properties/ERC4626/ERC4626PropertyTests.sol"; // contracts -import {EulerAggregationLayer} from "../../src/core/EulerAggregationLayer.sol"; +import {EulerAggregationVault} from "../../src/core/EulerAggregationVault.sol"; import {Rebalancer} from "../../src/plugin/Rebalancer.sol"; import {Hooks} from "../../src/core/module/Hooks.sol"; import {Rewards} from "../../src/core/module/Rewards.sol"; import {Fee} from "../../src/core/module/Fee.sol"; -import {EulerAggregationLayerFactory} from "../../src/core/EulerAggregationLayerFactory.sol"; +import {EulerAggregationVaultFactory} from "../../src/core/EulerAggregationVaultFactory.sol"; import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; import {AllocationPoints} from "../../src/core/module/AllocationPoints.sol"; import {TestERC20Token} from "crytic-properties/ERC4626/util/TestERC20Token.sol"; @@ -26,8 +26,8 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { Rebalancer rebalancerPlugin; WithdrawalQueue withdrawalQueuePluginImpl; - EulerAggregationLayerFactory eulerAggregationLayerFactory; - EulerAggregationLayer eulerAggregationLayer; + EulerAggregationVaultFactory eulerAggregationVaultFactory; + EulerAggregationVault eulerAggregationVault; constructor() { rewardsImpl = new Rewards(); @@ -38,7 +38,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { rebalancerPlugin = new Rebalancer(); withdrawalQueuePluginImpl = new WithdrawalQueue(); - EulerAggregationLayerFactory.FactoryParams memory factoryParams = EulerAggregationLayerFactory.FactoryParams({ + EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ balanceTracker: address(0), rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), @@ -47,10 +47,10 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { rebalancer: address(rebalancerPlugin), withdrawalQueueImpl: address(withdrawalQueuePluginImpl) }); - eulerAggregationLayerFactory = new EulerAggregationLayerFactory(factoryParams); + eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); TestERC20Token _asset = new TestERC20Token("Test Token", "TT", 18); - address _vault = eulerAggregationLayerFactory.deployEulerAggregationLayer( + address _vault = eulerAggregationVaultFactory.deployEulerAggregationVault( address(_asset), "TT_Agg", "TT_Agg", CASH_RESERVE_ALLOCATION_POINTS ); diff --git a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol index be8c0981..b9762996 100644 --- a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol +++ b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, - IEulerAggregationLayer -} from "../common/EulerAggregationLayerBase.t.sol"; + EulerAggregationVaultBase, + EulerAggregationVault, + IEulerAggregationVault +} from "../common/EulerAggregationVaultBase.t.sol"; -contract AdjustAllocationsPointsFuzzTest is EulerAggregationLayerBase { +contract AdjustAllocationsPointsFuzzTest is EulerAggregationVaultBase { function setUp() public virtual override { super.setUp(); @@ -18,23 +18,23 @@ contract AdjustAllocationsPointsFuzzTest is EulerAggregationLayerBase { function testFuzzAdjustAllocationPoints(uint256 _newAllocationPoints) public { _newAllocationPoints = bound(_newAllocationPoints, 1, type(uint120).max); - uint256 strategyAllocationPoints = (eulerAggregationLayer.getStrategy(address(eTST))).allocationPoints; - uint256 totalAllocationPointsBefore = eulerAggregationLayer.totalAllocationPoints(); + uint256 strategyAllocationPoints = (eulerAggregationVault.getStrategy(address(eTST))).allocationPoints; + uint256 totalAllocationPointsBefore = eulerAggregationVault.totalAllocationPoints(); uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); - eulerAggregationLayer.adjustAllocationPoints(address(eTST), _newAllocationPoints); + eulerAggregationVault.adjustAllocationPoints(address(eTST), _newAllocationPoints); - IEulerAggregationLayer.Strategy memory strategy = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategy = eulerAggregationVault.getStrategy(address(eTST)); if (_newAllocationPoints < strategyAllocationPoints) { assertEq( - eulerAggregationLayer.totalAllocationPoints(), + eulerAggregationVault.totalAllocationPoints(), totalAllocationPointsBefore - (strategyAllocationPoints - _newAllocationPoints) ); } else { assertEq( - eulerAggregationLayer.totalAllocationPoints(), + eulerAggregationVault.totalAllocationPoints(), totalAllocationPointsBefore + (_newAllocationPoints - strategyAllocationPoints) ); } diff --git a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol index fd18fcc0..56e49e93 100644 --- a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol +++ b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {console2, EulerAggregationLayerBase, EulerAggregationLayer} from "../common/EulerAggregationLayerBase.t.sol"; +import {console2, EulerAggregationVaultBase, EulerAggregationVault} from "../common/EulerAggregationVaultBase.t.sol"; -contract DepositWithdrawMintBurnFuzzTest is EulerAggregationLayerBase { +contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { uint256 constant MAX_ALLOWED = type(uint256).max; function setUp() public virtual override { @@ -14,16 +14,16 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationLayerBase { // moch the scenario of _assets ownership assetTST.mint(user1, _assets); - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); _deposit(user1, _assets); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + _assets); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + _assets); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + _assets); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + _assets); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + _assets); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + _assets); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - _assets); } @@ -45,36 +45,36 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationLayerBase { vm.warp(block.timestamp + _timestampAfterDeposit); // fuzz partial & full withdraws - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 receiverAssetBalanceBefore = assetTST.balanceOf(_receiver); vm.startPrank(user1); - eulerAggregationLayer.withdraw(_assetsToWithdraw, _receiver, user1); + eulerAggregationVault.withdraw(_assetsToWithdraw, _receiver, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore - _assetsToWithdraw); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore - _assetsToWithdraw); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _assetsToWithdraw); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _assetsToWithdraw); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + _assetsToWithdraw); } function testFuzzMint(uint256 _shares) public { // moch the scenario of _assets ownership - uint256 assets = eulerAggregationLayer.previewMint(_shares); + uint256 assets = eulerAggregationVault.previewMint(_shares); assetTST.mint(user1, assets); - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); _mint(user1, assets, _shares); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + _shares); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + _shares); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + assets); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + _shares); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + _shares); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + assets); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - assets); } @@ -91,38 +91,38 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationLayerBase { _timestampAfterDeposit = bound(_timestampAfterDeposit, 0, 86400); // deposit - uint256 assetsToDeposit = eulerAggregationLayer.previewMint(_sharesToMint); + uint256 assetsToDeposit = eulerAggregationVault.previewMint(_sharesToMint); assetTST.mint(user1, assetsToDeposit); _mint(user1, assetsToDeposit, _sharesToMint); vm.warp(block.timestamp + _timestampAfterDeposit); // fuzz partial & full redeem - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 receiverAssetBalanceBefore = assetTST.balanceOf(_receiver); vm.startPrank(user1); - uint256 assetsToWithdraw = eulerAggregationLayer.redeem(_sharesToRedeem, _receiver, user1); + uint256 assetsToWithdraw = eulerAggregationVault.redeem(_sharesToRedeem, _receiver, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore - _sharesToRedeem); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore - _sharesToRedeem); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _sharesToRedeem); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _sharesToRedeem); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + assetsToWithdraw); } function _deposit(address _from, uint256 _assets) private { vm.startPrank(_from); - assetTST.approve(address(eulerAggregationLayer), _assets); - eulerAggregationLayer.deposit(_assets, _from); + assetTST.approve(address(eulerAggregationVault), _assets); + eulerAggregationVault.deposit(_assets, _from); vm.stopPrank(); } function _mint(address _from, uint256 _assets, uint256 _shares) private { vm.startPrank(_from); - assetTST.approve(address(eulerAggregationLayer), _assets); - eulerAggregationLayer.mint(_shares, _from); + assetTST.approve(address(eulerAggregationVault), _assets); + eulerAggregationVault.mint(_shares, _from); vm.stopPrank(); } } diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 84b0c77e..622bdcac 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -2,23 +2,23 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, IWithdrawalQueue, IEVault, TestERC20 -} from "../common/EulerAggregationLayerBase.t.sol"; +} from "../common/EulerAggregationVaultBase.t.sol"; import {Actor} from "./util/Actor.sol"; import {Strategy} from "./util/Strategy.sol"; -import {EulerAggregationLayerHandler} from "./handler/EulerAggregationLayerHandler.sol"; +import {EulerAggregationVaultHandler} from "./handler/EulerAggregationVaultHandler.sol"; import {RebalancerHandler} from "./handler/RebalancerHandler.sol"; import {WithdrawalQueueHandler} from "./handler/WithdrawalQueueHandler.sol"; -contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { +contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { Actor internal actorUtil; Strategy internal strategyUtil; - EulerAggregationLayerHandler internal eulerAggregationLayerHandler; + EulerAggregationVaultHandler internal eulerAggregationVaultHandler; RebalancerHandler internal rebalancerHandler; WithdrawalQueueHandler internal withdrawalQueueHandler; @@ -47,52 +47,52 @@ contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { strategyUtil.includeStrategy(address(eTSTfifth)); strategyUtil.includeStrategy(address(eTSTsixth)); - eulerAggregationLayerHandler = - new EulerAggregationLayerHandler(eulerAggregationLayer, actorUtil, strategyUtil, withdrawalQueue); + eulerAggregationVaultHandler = + new EulerAggregationVaultHandler(eulerAggregationVault, actorUtil, strategyUtil, withdrawalQueue); rebalancerHandler = - new RebalancerHandler(eulerAggregationLayer, rebalancer, actorUtil, strategyUtil, withdrawalQueue); + new RebalancerHandler(eulerAggregationVault, rebalancer, actorUtil, strategyUtil, withdrawalQueue); withdrawalQueueHandler = - new WithdrawalQueueHandler(eulerAggregationLayer, actorUtil, strategyUtil, withdrawalQueue); + new WithdrawalQueueHandler(eulerAggregationVault, actorUtil, strategyUtil, withdrawalQueue); - targetContract(address(eulerAggregationLayerHandler)); + targetContract(address(eulerAggregationVaultHandler)); targetContract(address(rebalancerHandler)); targetContract(address(withdrawalQueueHandler)); } function invariant_gulp() public { - eulerAggregationLayer.gulp(); + eulerAggregationVault.gulp(); assertEq( - eulerAggregationLayer.totalAssetsAllocatable(), - eulerAggregationLayer.totalAssetsDeposited() - + (eulerAggregationLayer.getAggregationVaultSavingRate()).interestLeft + eulerAggregationVault.totalAssetsAllocatable(), + eulerAggregationVault.totalAssetsDeposited() + + (eulerAggregationVault.getAggregationVaultSavingRate()).interestLeft ); } function invariant_totalAllocationPoints() public view { - address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); + address withdrawalQueueAddr = eulerAggregationVault.withdrawalQueue(); (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); uint256 expectedTotalAllocationpoints; - expectedTotalAllocationpoints += (eulerAggregationLayer.getStrategy(address(0))).allocationPoints; + expectedTotalAllocationpoints += (eulerAggregationVault.getStrategy(address(0))).allocationPoints; for (uint256 i; i < withdrawalQueueLength; i++) { expectedTotalAllocationpoints += - (eulerAggregationLayer.getStrategy(withdrawalQueueArray[i])).allocationPoints; + (eulerAggregationVault.getStrategy(withdrawalQueueArray[i])).allocationPoints; } - assertEq(eulerAggregationLayer.totalAllocationPoints(), expectedTotalAllocationpoints); + assertEq(eulerAggregationVault.totalAllocationPoints(), expectedTotalAllocationpoints); } function invariant_withdrawalQueue() public view { - address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); + address withdrawalQueueAddr = eulerAggregationVault.withdrawalQueue(); (, uint256 withdrawalQueueLength) = IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); - uint256 cashReserveAllocationPoints = (eulerAggregationLayer.getStrategy(address(0))).allocationPoints; + uint256 cashReserveAllocationPoints = (eulerAggregationVault.getStrategy(address(0))).allocationPoints; - if (eulerAggregationLayer.totalAllocationPoints() - cashReserveAllocationPoints == 0) { + if (eulerAggregationVault.totalAllocationPoints() - cashReserveAllocationPoints == 0) { assertEq(withdrawalQueueLength, 0); } else { assertGt(withdrawalQueueLength, 0); @@ -100,26 +100,26 @@ contract EulerAggregationLayerInvariants is EulerAggregationLayerBase { } function invariant_totalAllocated() public view { - address withdrawalQueueAddr = eulerAggregationLayer.withdrawalQueue(); + address withdrawalQueueAddr = eulerAggregationVault.withdrawalQueue(); (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); uint256 aggregatedAllocatedAmount; for (uint256 i; i < withdrawalQueueLength; i++) { - aggregatedAllocatedAmount += (eulerAggregationLayer.getStrategy(withdrawalQueueArray[i])).allocated; + aggregatedAllocatedAmount += (eulerAggregationVault.getStrategy(withdrawalQueueArray[i])).allocated; } - assertEq(eulerAggregationLayer.totalAllocated(), aggregatedAllocatedAmount); + assertEq(eulerAggregationVault.totalAllocated(), aggregatedAllocatedAmount); } function invariant_performanceFee() public view { - for (uint256 i; i < eulerAggregationLayerHandler.ghostFeeRecipientsLength(); i++) { - address feeRecipient = eulerAggregationLayerHandler.ghost_feeRecipients(i); + for (uint256 i; i < eulerAggregationVaultHandler.ghostFeeRecipientsLength(); i++) { + address feeRecipient = eulerAggregationVaultHandler.ghost_feeRecipients(i); assertEq( assetTST.balanceOf(feeRecipient), - eulerAggregationLayerHandler.ghost_accumulatedPerformanceFeePerRecipient(feeRecipient) + eulerAggregationVaultHandler.ghost_accumulatedPerformanceFeePerRecipient(feeRecipient) ); } } diff --git a/test/invariant/handler/EulerAggregationLayerHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol similarity index 72% rename from test/invariant/handler/EulerAggregationLayerHandler.sol rename to test/invariant/handler/EulerAggregationVaultHandler.sol index 1cfb811d..471256be 100644 --- a/test/invariant/handler/EulerAggregationLayerHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -3,26 +3,26 @@ pragma solidity ^0.8.0; import { Test, - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, console2, EVault, IEVault, IRMTestDefault, TestERC20, - IEulerAggregationLayer, + IEulerAggregationVault, ErrorsLib, IERC4626, WithdrawalQueue -} from "../../common/EulerAggregationLayerBase.t.sol"; +} from "../../common/EulerAggregationVaultBase.t.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {Actor} from "../util/Actor.sol"; import {Strategy} from "../util/Strategy.sol"; -contract EulerAggregationLayerHandler is Test { +contract EulerAggregationVaultHandler is Test { Actor internal actorUtil; Strategy internal strategyUtil; - EulerAggregationLayer internal eulerAggLayer; + EulerAggregationVault internal eulerAggVault; WithdrawalQueue internal withdrawalQueue; // ghost vars @@ -39,18 +39,18 @@ contract EulerAggregationLayerHandler is Test { bytes returnData; constructor( - EulerAggregationLayer _eulerAggLayer, + EulerAggregationVault _eulerAggVault, Actor _actor, Strategy _strategy, WithdrawalQueue _withdrawalQueue ) { - eulerAggLayer = _eulerAggLayer; + eulerAggVault = _eulerAggVault; actorUtil = _actor; strategyUtil = _strategy; withdrawalQueue = _withdrawalQueue; // initiating ghost total allocation points to match count cash reserve. - ghost_totalAllocationPoints += eulerAggLayer.totalAllocationPoints(); + ghost_totalAllocationPoints += eulerAggVault.totalAllocationPoints(); ghost_allocationPoints[address(0)] = ghost_totalAllocationPoints; } @@ -60,23 +60,23 @@ contract EulerAggregationLayerHandler is Test { (currentActor, success, returnData) = actorUtil.initiateExactActorCall( 0, - address(eulerAggLayer), - abi.encodeWithSelector(EulerAggregationLayer.setFeeRecipient.selector, feeRecipientAddr) + address(eulerAggVault), + abi.encodeWithSelector(EulerAggregationVault.setFeeRecipient.selector, feeRecipientAddr) ); if (success) { ghost_feeRecipients.push(feeRecipientAddr); } - (address feeRecipient,) = eulerAggLayer.performanceFeeConfig(); + (address feeRecipient,) = eulerAggVault.performanceFeeConfig(); assertEq(feeRecipient, feeRecipientAddr); } function setPerformanceFee(uint256 _newFee) external { (currentActor, success, returnData) = actorUtil.initiateExactActorCall( - 0, address(eulerAggLayer), abi.encodeWithSelector(EulerAggregationLayer.setPerformanceFee.selector, _newFee) + 0, address(eulerAggVault), abi.encodeWithSelector(EulerAggregationVault.setPerformanceFee.selector, _newFee) ); - (, uint256 fee) = eulerAggLayer.performanceFeeConfig(); + (, uint256 fee) = eulerAggVault.performanceFeeConfig(); assertEq(_newFee, fee); } @@ -84,35 +84,35 @@ contract EulerAggregationLayerHandler is Test { function adjustAllocationPoints(uint256 _strategyIndexSeed, uint256 _newPoints) external { address strategyAddr = strategyUtil.fetchStrategy(_strategyIndexSeed); - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggLayer.getStrategy(strategyAddr); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggVault.getStrategy(strategyAddr); (currentActor, success, returnData) = actorUtil.initiateExactActorCall( 0, - address(eulerAggLayer), - abi.encodeWithSelector(IEulerAggregationLayer.adjustAllocationPoints.selector, strategyAddr, _newPoints) + address(eulerAggVault), + abi.encodeWithSelector(IEulerAggregationVault.adjustAllocationPoints.selector, strategyAddr, _newPoints) ); if (success) { ghost_totalAllocationPoints = ghost_totalAllocationPoints + _newPoints - strategyBefore.allocationPoints; ghost_allocationPoints[strategyAddr] = _newPoints; } - IEulerAggregationLayer.Strategy memory strategyAfter = eulerAggLayer.getStrategy(strategyAddr); - assertEq(eulerAggLayer.totalAllocationPoints(), ghost_totalAllocationPoints); + IEulerAggregationVault.Strategy memory strategyAfter = eulerAggVault.getStrategy(strategyAddr); + assertEq(eulerAggVault.totalAllocationPoints(), ghost_totalAllocationPoints); assertEq(strategyAfter.allocationPoints, ghost_allocationPoints[strategyAddr]); } function setStrategyCap(uint256 _strategyIndexSeed, uint256 _cap) external { address strategyAddr = strategyUtil.fetchStrategy(_strategyIndexSeed); - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggLayer.getStrategy(strategyAddr); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggVault.getStrategy(strategyAddr); (currentActor, success, returnData) = actorUtil.initiateExactActorCall( 0, - address(eulerAggLayer), - abi.encodeWithSelector(EulerAggregationLayer.setStrategyCap.selector, strategyAddr, _cap) + address(eulerAggVault), + abi.encodeWithSelector(EulerAggregationVault.setStrategyCap.selector, strategyAddr, _cap) ); - IEulerAggregationLayer.Strategy memory strategyAfter = eulerAggLayer.getStrategy(strategyAddr); + IEulerAggregationVault.Strategy memory strategyAfter = eulerAggVault.getStrategy(strategyAddr); if (success) { assertEq(strategyAfter.cap, _cap); } else { @@ -125,28 +125,28 @@ contract EulerAggregationLayerHandler is Test { (currentActor, success, returnData) = actorUtil.initiateExactActorCall( 0, - address(eulerAggLayer), - abi.encodeWithSelector(IEulerAggregationLayer.addStrategy.selector, strategyAddr, _allocationPoints) + address(eulerAggVault), + abi.encodeWithSelector(IEulerAggregationVault.addStrategy.selector, strategyAddr, _allocationPoints) ); if (success) { ghost_totalAllocationPoints += _allocationPoints; ghost_allocationPoints[strategyAddr] = _allocationPoints; } - IEulerAggregationLayer.Strategy memory strategyAfter = eulerAggLayer.getStrategy(strategyAddr); - assertEq(eulerAggLayer.totalAllocationPoints(), ghost_totalAllocationPoints); + IEulerAggregationVault.Strategy memory strategyAfter = eulerAggVault.getStrategy(strategyAddr); + assertEq(eulerAggVault.totalAllocationPoints(), ghost_totalAllocationPoints); assertEq(strategyAfter.allocationPoints, ghost_allocationPoints[strategyAddr]); } function removeStrategy(uint256 _strategyIndexSeed) external { address strategyAddr = strategyUtil.fetchStrategy(_strategyIndexSeed); - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggLayer.getStrategy(strategyAddr); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggVault.getStrategy(strategyAddr); (currentActor, success, returnData) = actorUtil.initiateExactActorCall( 0, - address(eulerAggLayer), - abi.encodeWithSelector(IEulerAggregationLayer.removeStrategy.selector, strategyAddr) + address(eulerAggVault), + abi.encodeWithSelector(IEulerAggregationVault.removeStrategy.selector, strategyAddr) ); if (success) { @@ -154,14 +154,14 @@ contract EulerAggregationLayerHandler is Test { ghost_allocationPoints[strategyAddr] = 0; } - IEulerAggregationLayer.Strategy memory strategyAfter = eulerAggLayer.getStrategy(strategyAddr); + IEulerAggregationVault.Strategy memory strategyAfter = eulerAggVault.getStrategy(strategyAddr); assertEq(strategyAfter.allocationPoints, ghost_allocationPoints[strategyAddr]); - assertEq(eulerAggLayer.totalAllocationPoints(), ghost_totalAllocationPoints); + assertEq(eulerAggVault.totalAllocationPoints(), ghost_totalAllocationPoints); } function harvest(uint256 _actorIndexSeed) external { // check if performance fee is on; store received fee per recipient if call is succesfull - (address feeRecipient, uint256 performanceFee) = eulerAggLayer.performanceFeeConfig(); + (address feeRecipient, uint256 performanceFee) = eulerAggVault.performanceFeeConfig(); uint256 accumulatedPerformanceFee; if (feeRecipient != address(0) && performanceFee > 0) { accumulatedPerformanceFee = ghost_accumulatedPerformanceFeePerRecipient[feeRecipient]; @@ -169,8 +169,8 @@ contract EulerAggregationLayerHandler is Test { withdrawalQueue.getWithdrawalQueueArray(); for (uint256 i; i < withdrawalQueueLength; i++) { - uint256 allocated = (eulerAggLayer.getStrategy(withdrawalQueueArray[i])).allocated; - uint256 underlying = IERC4626(withdrawalQueueArray[i]).maxWithdraw(address(eulerAggLayer)); + uint256 allocated = (eulerAggVault.getStrategy(withdrawalQueueArray[i])).allocated; + uint256 underlying = IERC4626(withdrawalQueueArray[i]).maxWithdraw(address(eulerAggVault)); if (underlying > allocated) { uint256 yield = underlying - allocated; accumulatedPerformanceFee += Math.mulDiv(yield, performanceFee, 1e18, Math.Rounding.Floor); @@ -179,7 +179,7 @@ contract EulerAggregationLayerHandler is Test { } (, success, returnData) = actorUtil.initiateActorCall( - _actorIndexSeed, address(eulerAggLayer), abi.encodeWithSelector(IEulerAggregationLayer.harvest.selector) + _actorIndexSeed, address(eulerAggVault), abi.encodeWithSelector(IEulerAggregationVault.harvest.selector) ); if (success) { @@ -190,20 +190,20 @@ contract EulerAggregationLayerHandler is Test { function updateInterestAccrued(uint256 _actorIndexSeed) external { (, success, returnData) = actorUtil.initiateActorCall( _actorIndexSeed, - address(eulerAggLayer), - abi.encodeWithSelector(EulerAggregationLayer.updateInterestAccrued.selector) + address(eulerAggVault), + abi.encodeWithSelector(EulerAggregationVault.updateInterestAccrued.selector) ); } function gulp(uint256 _actorIndexSeed) external { (, success, returnData) = actorUtil.initiateActorCall( - _actorIndexSeed, address(eulerAggLayer), abi.encodeWithSelector(EulerAggregationLayer.gulp.selector) + _actorIndexSeed, address(eulerAggVault), abi.encodeWithSelector(EulerAggregationVault.gulp.selector) ); if (success) { assertEq( - eulerAggLayer.totalAssetsAllocatable(), - eulerAggLayer.totalAssetsDeposited() + (eulerAggLayer.getAggregationVaultSavingRate()).interestLeft + eulerAggVault.totalAssetsAllocatable(), + eulerAggVault.totalAssetsDeposited() + (eulerAggVault.getAggregationVaultSavingRate()).interestLeft ); } } @@ -213,18 +213,18 @@ contract EulerAggregationLayerHandler is Test { (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); - _fillBalance(currentActor, eulerAggLayer.asset(), _assets); + _fillBalance(currentActor, eulerAggVault.asset(), _assets); (currentActor, success, returnData) = actorUtil.initiateExactActorCall( currentActorIndex, - address(eulerAggLayer), + address(eulerAggVault), abi.encodeWithSelector(IERC4626.deposit.selector, _assets, _receiver) ); if (success) { ghost_totalAssetsDeposited += _assets; } - assertEq(eulerAggLayer.totalAssetsDeposited(), ghost_totalAssetsDeposited); + assertEq(eulerAggVault.totalAssetsDeposited(), ghost_totalAssetsDeposited); } function mint(uint256 _actorIndexSeed, uint256 _shares, address _receiver) external { @@ -232,19 +232,19 @@ contract EulerAggregationLayerHandler is Test { (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); - uint256 assets = eulerAggLayer.previewMint(_shares); - _fillBalance(currentActor, eulerAggLayer.asset(), assets); + uint256 assets = eulerAggVault.previewMint(_shares); + _fillBalance(currentActor, eulerAggVault.asset(), assets); (currentActor, success, returnData) = actorUtil.initiateExactActorCall( currentActorIndex, - address(eulerAggLayer), + address(eulerAggVault), abi.encodeWithSelector(IERC4626.mint.selector, _shares, _receiver) ); if (success) { ghost_totalAssetsDeposited += assets; } - assertEq(eulerAggLayer.totalAssetsDeposited(), ghost_totalAssetsDeposited); + assertEq(eulerAggVault.totalAssetsDeposited(), ghost_totalAssetsDeposited); } function withdraw(uint256 _actorIndexSeed, uint256 _assets, address _receiver) external { @@ -254,14 +254,14 @@ contract EulerAggregationLayerHandler is Test { (currentActor, success, returnData) = actorUtil.initiateExactActorCall( currentActorIndex, - address(eulerAggLayer), + address(eulerAggVault), abi.encodeWithSelector(IERC4626.withdraw.selector, _assets, _receiver, currentActor) ); if (success) { ghost_totalAssetsDeposited -= _assets; } - assertEq(eulerAggLayer.totalAssetsDeposited(), ghost_totalAssetsDeposited); + assertEq(eulerAggVault.totalAssetsDeposited(), ghost_totalAssetsDeposited); } function redeem(uint256 _actorIndexSeed, uint256 _shares, address _receiver) external { @@ -271,7 +271,7 @@ contract EulerAggregationLayerHandler is Test { (currentActor, success, returnData) = actorUtil.initiateExactActorCall( currentActorIndex, - address(eulerAggLayer), + address(eulerAggVault), abi.encodeWithSelector(IERC4626.redeem.selector, _shares, _receiver, currentActor) ); @@ -279,7 +279,7 @@ contract EulerAggregationLayerHandler is Test { uint256 assets = abi.decode(returnData, (uint256)); ghost_totalAssetsDeposited -= assets; } - assertEq(eulerAggLayer.totalAssetsDeposited(), ghost_totalAssetsDeposited); + assertEq(eulerAggVault.totalAssetsDeposited(), ghost_totalAssetsDeposited); } function ghostFeeRecipientsLength() external view returns (uint256) { @@ -294,6 +294,6 @@ contract EulerAggregationLayerHandler is Test { asset.mint(currentActor, _amount - actorCurrentBalance); } vm.prank(_actor); - asset.approve(address(eulerAggLayer), _amount); + asset.approve(address(eulerAggVault), _amount); } } diff --git a/test/invariant/handler/RebalancerHandler.sol b/test/invariant/handler/RebalancerHandler.sol index d57bc7cb..fa861537 100644 --- a/test/invariant/handler/RebalancerHandler.sol +++ b/test/invariant/handler/RebalancerHandler.sol @@ -3,25 +3,25 @@ pragma solidity ^0.8.0; import { Test, - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, EVault, IEVault, IRMTestDefault, TestERC20, - IEulerAggregationLayer, + IEulerAggregationVault, ErrorsLib, IERC4626, Rebalancer, WithdrawalQueue -} from "../../common/EulerAggregationLayerBase.t.sol"; +} from "../../common/EulerAggregationVaultBase.t.sol"; import {Actor} from "../util/Actor.sol"; import {Strategy} from "../util/Strategy.sol"; contract RebalancerHandler is Test { Actor internal actorUtil; Strategy internal strategyUtil; - EulerAggregationLayer internal eulerAggLayer; + EulerAggregationVault internal eulerAggVault; Rebalancer internal rebalancer; WithdrawalQueue internal withdrawalQueue; @@ -32,13 +32,13 @@ contract RebalancerHandler is Test { bytes returnData; constructor( - EulerAggregationLayer _eulerAggLayer, + EulerAggregationVault _eulerAggVault, Rebalancer _rebalancer, Actor _actor, Strategy _strategy, WithdrawalQueue _withdrawalQueue ) { - eulerAggLayer = _eulerAggLayer; + eulerAggVault = _eulerAggVault; actorUtil = _actor; strategyUtil = _strategy; rebalancer = _rebalancer; @@ -52,13 +52,13 @@ contract RebalancerHandler is Test { (currentActor, success, returnData) = actorUtil.initiateActorCall( _actorIndexSeed, address(rebalancer), - abi.encodeWithSelector(Rebalancer.executeRebalance.selector, address(eulerAggLayer), strategiesToRebalance) + abi.encodeWithSelector(Rebalancer.executeRebalance.selector, address(eulerAggVault), strategiesToRebalance) ); for (uint256 i; i < strategiesCounter; i++) { assertEq( - IERC4626(strategiesToRebalance[i]).maxWithdraw(address(eulerAggLayer)), - (eulerAggLayer.getStrategy(strategiesToRebalance[i])).allocated + IERC4626(strategiesToRebalance[i]).maxWithdraw(address(eulerAggVault)), + (eulerAggVault.getStrategy(strategiesToRebalance[i])).allocated ); } } diff --git a/test/invariant/handler/WithdrawalQueueHandler.sol b/test/invariant/handler/WithdrawalQueueHandler.sol index 0c9e165f..e884af88 100644 --- a/test/invariant/handler/WithdrawalQueueHandler.sol +++ b/test/invariant/handler/WithdrawalQueueHandler.sol @@ -3,26 +3,26 @@ pragma solidity ^0.8.0; import { Test, - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, console2, EVault, IEVault, IRMTestDefault, TestERC20, - IEulerAggregationLayer, + IEulerAggregationVault, ErrorsLib, IERC4626, Rebalancer, WithdrawalQueue -} from "../../common/EulerAggregationLayerBase.t.sol"; +} from "../../common/EulerAggregationVaultBase.t.sol"; import {Actor} from "../util/Actor.sol"; import {Strategy} from "../util/Strategy.sol"; contract WithdrawalQueueHandler is Test { Actor internal actorUtil; Strategy internal strategyUtil; - EulerAggregationLayer internal eulerAggLayer; + EulerAggregationVault internal eulerAggVault; WithdrawalQueue internal withdrawalQueue; // last function call state @@ -32,12 +32,12 @@ contract WithdrawalQueueHandler is Test { bytes returnData; constructor( - EulerAggregationLayer _eulerAggLayer, + EulerAggregationVault _eulerAggVault, Actor _actor, Strategy _strategy, WithdrawalQueue _withdrawalQueue ) { - eulerAggLayer = _eulerAggLayer; + eulerAggVault = _eulerAggVault; actorUtil = _actor; strategyUtil = _strategy; withdrawalQueue = _withdrawalQueue; diff --git a/test/invariant/util/Actor.sol b/test/invariant/util/Actor.sol index 149aa9e1..fe858335 100644 --- a/test/invariant/util/Actor.sol +++ b/test/invariant/util/Actor.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import {Test} from "forge-std/Test.sol"; contract Actor is Test { - /// @dev actor[0] will always be a manager address that have access to all EulerAggregationLayer roles. + /// @dev actor[0] will always be a manager address that have access to all EulerAggregationVault roles. address[] public actors; function includeActor(address _actor) external { diff --git a/test/unit/AddStrategyTest.t.sol b/test/unit/AddStrategyTest.t.sol index 0e00a089..1b05ad80 100644 --- a/test/unit/AddStrategyTest.t.sol +++ b/test/unit/AddStrategyTest.t.sol @@ -1,22 +1,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {EulerAggregationLayerBase, EulerAggregationLayer} from "../common/EulerAggregationLayerBase.t.sol"; +import {EulerAggregationVaultBase, EulerAggregationVault} from "../common/EulerAggregationVaultBase.t.sol"; -contract AddStrategyTest is EulerAggregationLayerBase { +contract AddStrategyTest is EulerAggregationVaultBase { function setUp() public virtual override { super.setUp(); } function testAddStrategy() public { uint256 allocationPoints = 500e18; - uint256 totalAllocationPointsBefore = eulerAggregationLayer.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = eulerAggregationVault.totalAllocationPoints(); assertEq(_getWithdrawalQueueLength(), 0); _addStrategy(manager, address(eTST), allocationPoints); - assertEq(eulerAggregationLayer.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); + assertEq(eulerAggregationVault.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); assertEq(_getWithdrawalQueueLength(), 1); } @@ -40,13 +40,13 @@ contract AddStrategyTest is EulerAggregationLayerBase { function testAddStrategy_AlreadyAddedStrategy() public { uint256 allocationPoints = 500e18; - uint256 totalAllocationPointsBefore = eulerAggregationLayer.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = eulerAggregationVault.totalAllocationPoints(); assertEq(_getWithdrawalQueueLength(), 0); _addStrategy(manager, address(eTST), allocationPoints); - assertEq(eulerAggregationLayer.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); + assertEq(eulerAggregationVault.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); assertEq(_getWithdrawalQueueLength(), 1); vm.expectRevert(); diff --git a/test/unit/AdjustAllocationPointsTest.t.sol b/test/unit/AdjustAllocationPointsTest.t.sol index 2b76fc00..049f6110 100644 --- a/test/unit/AdjustAllocationPointsTest.t.sol +++ b/test/unit/AdjustAllocationPointsTest.t.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, - IEulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, + IEulerAggregationVault, ErrorsLib -} from "../common/EulerAggregationLayerBase.t.sol"; +} from "../common/EulerAggregationVaultBase.t.sol"; -contract AdjustAllocationsPointsTest is EulerAggregationLayerBase { +contract AdjustAllocationsPointsTest is EulerAggregationVaultBase { uint256 initialStrategyAllocationPoints = 500e18; function setUp() public virtual override { @@ -19,16 +19,16 @@ contract AdjustAllocationsPointsTest is EulerAggregationLayerBase { function testAdjustAllocationPoints() public { uint256 newAllocationPoints = 859e18; - uint256 totalAllocationPointsBefore = eulerAggregationLayer.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = eulerAggregationVault.totalAllocationPoints(); uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); - eulerAggregationLayer.adjustAllocationPoints(address(eTST), newAllocationPoints); + eulerAggregationVault.adjustAllocationPoints(address(eTST), newAllocationPoints); - IEulerAggregationLayer.Strategy memory strategy = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategy = eulerAggregationVault.getStrategy(address(eTST)); assertEq( - eulerAggregationLayer.totalAllocationPoints(), + eulerAggregationVault.totalAllocationPoints(), totalAllocationPointsBefore + (newAllocationPoints - initialStrategyAllocationPoints) ); assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore); @@ -40,7 +40,7 @@ contract AdjustAllocationsPointsTest is EulerAggregationLayerBase { vm.startPrank(deployer); vm.expectRevert(); - eulerAggregationLayer.adjustAllocationPoints(address(eTST), newAllocationPoints); + eulerAggregationVault.adjustAllocationPoints(address(eTST), newAllocationPoints); vm.stopPrank(); } @@ -49,7 +49,7 @@ contract AdjustAllocationsPointsTest is EulerAggregationLayerBase { vm.startPrank(manager); vm.expectRevert(ErrorsLib.InactiveStrategy.selector); - eulerAggregationLayer.adjustAllocationPoints(address(eTST2), newAllocationPoints); + eulerAggregationVault.adjustAllocationPoints(address(eTST2), newAllocationPoints); vm.stopPrank(); } } diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index 40dd9f8e..85e34967 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, console2, EVault, - IEulerAggregationLayer -} from "../common/EulerAggregationLayerBase.t.sol"; + IEulerAggregationVault +} from "../common/EulerAggregationVaultBase.t.sol"; -contract GulpTest is EulerAggregationLayerBase { +contract GulpTest is EulerAggregationVaultBase { uint256 user1InitialBalance = 100000e18; uint256 amountToDeposit = 10000e18; @@ -23,142 +23,142 @@ contract GulpTest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); } } function testGulpAfterNegativeYieldEqualToInterestLeft() public { - eulerAggregationLayer.gulp(); - EulerAggregationLayer.AggregationVaultSavingRate memory ers = - eulerAggregationLayer.getAggregationVaultSavingRate(); - assertEq(eulerAggregationLayer.interestAccrued(), 0); + eulerAggregationVault.gulp(); + EulerAggregationVault.AggregationVaultSavingRate memory ers = + eulerAggregationVault.getAggregationVaultSavingRate(); + assertEq(eulerAggregationVault.interestAccrued(), 0); assertEq(ers.interestLeft, 0); vm.warp(block.timestamp + 2 days); - eulerAggregationLayer.gulp(); - assertEq(eulerAggregationLayer.interestAccrued(), 0); + eulerAggregationVault.gulp(); + assertEq(eulerAggregationVault.interestAccrued(), 0); vm.warp(block.timestamp + 1 days); - assertEq(eulerAggregationLayer.interestAccrued(), 0); + assertEq(eulerAggregationVault.interestAccrued(), 0); uint256 yield; { - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(eulerAggregationLayer)); + eTST.skim(type(uint256).max, address(eulerAggregationVault)); } vm.prank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); - assertEq(eulerAggregationLayer.interestAccrued(), 0); + assertEq(eulerAggregationVault.interestAccrued(), 0); vm.warp(block.timestamp + 1 days); // interest per day 23.809523809523 - assertEq(eulerAggregationLayer.interestAccrued(), 23809523809523809523); - eulerAggregationLayer.gulp(); - ers = eulerAggregationLayer.getAggregationVaultSavingRate(); + assertEq(eulerAggregationVault.interestAccrued(), 23809523809523809523); + eulerAggregationVault.gulp(); + ers = eulerAggregationVault.getAggregationVaultSavingRate(); assertEq(ers.interestLeft, yield - 23809523809523809523); // move close to end of smearing vm.warp(block.timestamp + 11 days); - eulerAggregationLayer.gulp(); - ers = eulerAggregationLayer.getAggregationVaultSavingRate(); + eulerAggregationVault.gulp(); + ers = eulerAggregationVault.getAggregationVaultSavingRate(); // mock a decrease of strategy balance by ers.interestLeft - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - ers.interestLeft; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(eulerAggregationLayer)), + abi.encodeWithSelector(EVault.balanceOf.selector, address(eulerAggregationVault)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); vm.prank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); } function testGulpAfterNegativeYieldBiggerThanInterestLeft() public { - eulerAggregationLayer.gulp(); - EulerAggregationLayer.AggregationVaultSavingRate memory ers = - eulerAggregationLayer.getAggregationVaultSavingRate(); - assertEq(eulerAggregationLayer.interestAccrued(), 0); + eulerAggregationVault.gulp(); + EulerAggregationVault.AggregationVaultSavingRate memory ers = + eulerAggregationVault.getAggregationVaultSavingRate(); + assertEq(eulerAggregationVault.interestAccrued(), 0); assertEq(ers.interestLeft, 0); vm.warp(block.timestamp + 2 days); - eulerAggregationLayer.gulp(); - assertEq(eulerAggregationLayer.interestAccrued(), 0); + eulerAggregationVault.gulp(); + assertEq(eulerAggregationVault.interestAccrued(), 0); vm.warp(block.timestamp + 1 days); - assertEq(eulerAggregationLayer.interestAccrued(), 0); + assertEq(eulerAggregationVault.interestAccrued(), 0); uint256 yield; { - uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyShareBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrentStrategyUnderlyingBalance = eTST.convertToAssets(aggrCurrentStrategyShareBalance); uint256 aggrNewStrategyUnderlyingBalance = aggrCurrentStrategyUnderlyingBalance * 11e17 / 1e18; yield = aggrNewStrategyUnderlyingBalance - aggrCurrentStrategyUnderlyingBalance; assetTST.mint(address(eTST), yield); - eTST.skim(type(uint256).max, address(eulerAggregationLayer)); + eTST.skim(type(uint256).max, address(eulerAggregationVault)); } vm.prank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); - assertEq(eulerAggregationLayer.interestAccrued(), 0); + assertEq(eulerAggregationVault.interestAccrued(), 0); vm.warp(block.timestamp + 1 days); // interest per day 23.809523809523 - assertEq(eulerAggregationLayer.interestAccrued(), 23809523809523809523); - eulerAggregationLayer.gulp(); - ers = eulerAggregationLayer.getAggregationVaultSavingRate(); + assertEq(eulerAggregationVault.interestAccrued(), 23809523809523809523); + eulerAggregationVault.gulp(); + ers = eulerAggregationVault.getAggregationVaultSavingRate(); assertEq(ers.interestLeft, yield - 23809523809523809523); // move close to end of smearing vm.warp(block.timestamp + 11 days); - eulerAggregationLayer.gulp(); - ers = eulerAggregationLayer.getAggregationVaultSavingRate(); + eulerAggregationVault.gulp(); + ers = eulerAggregationVault.getAggregationVaultSavingRate(); // mock a decrease of strategy balance by ers.interestLeft - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance - (ers.interestLeft * 2); vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.balanceOf.selector, address(eulerAggregationLayer)), + abi.encodeWithSelector(EVault.balanceOf.selector, address(eulerAggregationVault)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); vm.prank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); } } diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index 2a864a23..369cfe3f 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, console2, EVault, - IEulerAggregationLayer -} from "../common/EulerAggregationLayerBase.t.sol"; + IEulerAggregationVault +} from "../common/EulerAggregationVaultBase.t.sol"; -contract HarvestTest is EulerAggregationLayerBase { +contract HarvestTest is EulerAggregationVaultBase { uint256 user1InitialBalance = 100000e18; uint256 amountToDeposit = 10000e18; @@ -23,74 +23,74 @@ contract HarvestTest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); { - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() - * strategyBefore.allocationPoints / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedStrategyCash); + assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); } } function testHarvestWithPositiveYield() public { // no yield increase - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - uint256 totalAllocatedBefore = eulerAggregationLayer.totalAllocated(); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); + uint256 totalAllocatedBefore = eulerAggregationVault.totalAllocated(); - assertTrue(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))) == strategyBefore.allocated); + assertTrue(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))) == strategyBefore.allocated); vm.prank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated); - assertEq(eulerAggregationLayer.totalAllocated(), totalAllocatedBefore); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated); + assertEq(eulerAggregationVault.totalAllocated(), totalAllocatedBefore); vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationVault)); vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationVault)), abi.encode(aggrCurrentStrategyBalance * 11e17 / 1e18) ); - uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationVault)); assertTrue(expectedAllocated > strategyBefore.allocated); vm.prank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedAllocated); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedAllocated); assertEq( - eulerAggregationLayer.totalAllocated(), + eulerAggregationVault.totalAllocated(), totalAllocatedBefore + (expectedAllocated - strategyBefore.allocated) ); } @@ -99,57 +99,57 @@ contract HarvestTest is EulerAggregationLayerBase { vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationVault)); vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationVault)), abi.encode(aggrCurrentStrategyBalance * 9e17 / 1e18) ); - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationVault)); assertTrue(expectedAllocated < strategyBefore.allocated); uint256 expectedLoss = strategyBefore.allocated - expectedAllocated; - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); vm.prank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); // check that loss socialized from the deposits - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedLoss); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedLoss); } function testHarvestNegativeYieldSingleUser() public { vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationVault)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationVault)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); assertTrue(expectedAllocated < strategyBefore.allocated); - uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(eulerAggregationLayer)); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(eulerAggregationVault)); - uint256 user1SharesBefore = eulerAggregationLayer.balanceOf(user1); - uint256 user1SocializedLoss = user1SharesBefore * negativeYield / eulerAggregationLayer.totalSupply(); + uint256 user1SharesBefore = eulerAggregationVault.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / eulerAggregationVault.totalSupply(); uint256 expectedUser1Assets = - user1SharesBefore * amountToDeposit / eulerAggregationLayer.totalSupply() - user1SocializedLoss; + user1SharesBefore * amountToDeposit / eulerAggregationVault.totalSupply() - user1SocializedLoss; vm.startPrank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); vm.stopPrank(); - uint256 user1SharesAfter = eulerAggregationLayer.balanceOf(user1); - uint256 user1AssetsAfter = eulerAggregationLayer.convertToAssets(user1SharesAfter); + uint256 user1SharesAfter = eulerAggregationVault.balanceOf(user1); + uint256 user1AssetsAfter = eulerAggregationVault.convertToAssets(user1SharesAfter); assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets, 1); } @@ -160,71 +160,71 @@ contract HarvestTest is EulerAggregationLayerBase { // deposit into aggregator { vm.startPrank(user2); - assetTST.approve(address(eulerAggregationLayer), user2InitialBalance); - eulerAggregationLayer.deposit(user2InitialBalance, user2); + assetTST.approve(address(eulerAggregationVault), user2InitialBalance); + eulerAggregationVault.deposit(user2InitialBalance, user2); vm.stopPrank(); } vm.warp(block.timestamp + 86400); // mock a decrease of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationVault)); uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationVault)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationVault)); assertTrue(expectedAllocated < strategyBefore.allocated); - uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(eulerAggregationLayer)); - uint256 user1SharesBefore = eulerAggregationLayer.balanceOf(user1); - uint256 user1SocializedLoss = user1SharesBefore * negativeYield / eulerAggregationLayer.totalSupply(); - uint256 user2SharesBefore = eulerAggregationLayer.balanceOf(user2); - uint256 user2SocializedLoss = user2SharesBefore * negativeYield / eulerAggregationLayer.totalSupply(); + uint256 negativeYield = strategyBefore.allocated - eTST.maxWithdraw(address(eulerAggregationVault)); + uint256 user1SharesBefore = eulerAggregationVault.balanceOf(user1); + uint256 user1SocializedLoss = user1SharesBefore * negativeYield / eulerAggregationVault.totalSupply(); + uint256 user2SharesBefore = eulerAggregationVault.balanceOf(user2); + uint256 user2SocializedLoss = user2SharesBefore * negativeYield / eulerAggregationVault.totalSupply(); uint256 expectedUser1Assets = user1SharesBefore * (amountToDeposit + user2InitialBalance) - / eulerAggregationLayer.totalSupply() - user1SocializedLoss; + / eulerAggregationVault.totalSupply() - user1SocializedLoss; uint256 expectedUser2Assets = user2SharesBefore * (amountToDeposit + user2InitialBalance) - / eulerAggregationLayer.totalSupply() - user2SocializedLoss; + / eulerAggregationVault.totalSupply() - user2SocializedLoss; vm.prank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); - uint256 user1SharesAfter = eulerAggregationLayer.balanceOf(user1); - uint256 user1AssetsAfter = eulerAggregationLayer.convertToAssets(user1SharesAfter); - uint256 user2SharesAfter = eulerAggregationLayer.balanceOf(user2); - uint256 user2AssetsAfter = eulerAggregationLayer.convertToAssets(user2SharesAfter); + uint256 user1SharesAfter = eulerAggregationVault.balanceOf(user1); + uint256 user1AssetsAfter = eulerAggregationVault.convertToAssets(user1SharesAfter); + uint256 user2SharesAfter = eulerAggregationVault.balanceOf(user2); + uint256 user2AssetsAfter = eulerAggregationVault.convertToAssets(user2SharesAfter); assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets, 1); assertApproxEqAbs(user2AssetsAfter, expectedUser2Assets, 1); } function testHarvestWhenInteresetLeftGreaterThanLoss() public { - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - uint256 totalAllocatedBefore = eulerAggregationLayer.totalAllocated(); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); + uint256 totalAllocatedBefore = eulerAggregationVault.totalAllocated(); vm.warp(block.timestamp + 86400); // mock an increase of strategy balance by 10% - uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationLayer)); + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationVault)); vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationVault)), abi.encode(aggrCurrentStrategyBalance * 11e17 / 1e18) ); - uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); + uint256 expectedAllocated = eTST.maxWithdraw(address(eulerAggregationVault)); assertTrue(expectedAllocated > strategyBefore.allocated); vm.prank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, expectedAllocated); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedAllocated); assertEq( - eulerAggregationLayer.totalAllocated(), + eulerAggregationVault.totalAllocated(), totalAllocatedBefore + (expectedAllocated - strategyBefore.allocated) ); @@ -234,27 +234,27 @@ contract HarvestTest is EulerAggregationLayerBase { uint256 aggrCurrentStrategyBalanceAfterNegYield = expectedAllocated * 98e16 / 1e18; vm.mockCall( address(eTST), - abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationLayer)), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationVault)), abi.encode(aggrCurrentStrategyBalanceAfterNegYield) ); - strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); - expectedAllocated = eTST.maxWithdraw(address(eulerAggregationLayer)); + strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); + expectedAllocated = eTST.maxWithdraw(address(eulerAggregationVault)); assertTrue(expectedAllocated < strategyBefore.allocated); - uint256 user1SharesBefore = eulerAggregationLayer.balanceOf(user1); - uint256 expectedUser1Assets = user1SharesBefore * amountToDeposit / eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); - uint256 interestToBeAccrued = eulerAggregationLayer.interestAccrued(); + uint256 user1SharesBefore = eulerAggregationVault.balanceOf(user1); + uint256 expectedUser1Assets = user1SharesBefore * amountToDeposit / eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + uint256 interestToBeAccrued = eulerAggregationVault.interestAccrued(); vm.startPrank(user1); - eulerAggregationLayer.harvest(); + eulerAggregationVault.harvest(); vm.stopPrank(); - uint256 user1SharesAfter = eulerAggregationLayer.balanceOf(user1); - uint256 user1AssetsAfter = eulerAggregationLayer.convertToAssets(user1SharesAfter); + uint256 user1SharesAfter = eulerAggregationVault.balanceOf(user1); + uint256 user1AssetsAfter = eulerAggregationVault.convertToAssets(user1SharesAfter); assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets + interestToBeAccrued, 1); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + interestToBeAccrued); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + interestToBeAccrued); } } diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index 1206633e..0fe60eab 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, console2, EVault, IEVault, IRMTestDefault, TestERC20, - IEulerAggregationLayer, + IEulerAggregationVault, ErrorsLib -} from "../common/EulerAggregationLayerBase.t.sol"; +} from "../common/EulerAggregationVaultBase.t.sol"; -contract RebalanceTest is EulerAggregationLayerBase { +contract RebalanceTest is EulerAggregationVaultBase { uint256 user1InitialBalance = 100000e18; function setUp() public virtual override { @@ -30,40 +30,40 @@ contract RebalanceTest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() * strategyBefore.allocationPoints - / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() * strategyBefore.allocationPoints + / eulerAggregationVault.totalAllocationPoints(); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); + assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); assertEq( - (eulerAggregationLayer.getStrategy(address(eTST))).allocated, + (eulerAggregationVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated + expectedStrategyCash ); } @@ -73,46 +73,46 @@ contract RebalanceTest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() * strategyBefore.allocationPoints - / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() * strategyBefore.allocationPoints + / eulerAggregationVault.totalAllocationPoints(); uint256 expectedToDeposit = expectedStrategyCash - strategyBefore.allocated; uint256 eTSTMaxDeposit = expectedToDeposit * 7e17 / 1e18; // mock max deposit vm.mockCall( - address(eTST), abi.encodeCall(eTST.maxDeposit, (address(eulerAggregationLayer))), abi.encode(eTSTMaxDeposit) + address(eTST), abi.encodeCall(eTST.maxDeposit, (address(eulerAggregationVault))), abi.encode(eTSTMaxDeposit) ); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - assertEq(eulerAggregationLayer.totalAllocated(), eTSTMaxDeposit); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), eTSTMaxDeposit); + assertEq(eulerAggregationVault.totalAllocated(), eTSTMaxDeposit); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), eTSTMaxDeposit); assertEq( - (eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated + eTSTMaxDeposit + (eulerAggregationVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated + eTSTMaxDeposit ); } @@ -122,19 +122,19 @@ contract RebalanceTest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -142,7 +142,7 @@ contract RebalanceTest is EulerAggregationLayerBase { vm.warp(block.timestamp + 86400); vm.prank(user1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); // create new strategy & add it IEVault eTSTsecondary; @@ -159,32 +159,32 @@ contract RebalanceTest is EulerAggregationLayerBase { // rebalance into eTSTsecondary vm.warp(block.timestamp + 86400); { - IEulerAggregationLayer.Strategy memory strategyBefore = - eulerAggregationLayer.getStrategy(address(eTSTsecondary)); + IEulerAggregationVault.Strategy memory strategyBefore = + eulerAggregationVault.getStrategy(address(eTSTsecondary)); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated ); - uint256 targetCash = eulerAggregationLayer.totalAssetsAllocatable() - * eulerAggregationLayer.getStrategy(address(0)).allocationPoints - / eulerAggregationLayer.totalAllocationPoints(); + uint256 targetCash = eulerAggregationVault.totalAssetsAllocatable() + * eulerAggregationVault.getStrategy(address(0)).allocationPoints + / eulerAggregationVault.totalAllocationPoints(); uint256 currentCash = - eulerAggregationLayer.totalAssetsAllocatable() - eulerAggregationLayer.totalAllocated(); + eulerAggregationVault.totalAssetsAllocatable() - eulerAggregationVault.totalAllocated(); uint256 expectedStrategyCash = currentCash - targetCash; vm.prank(user1); strategiesToRebalance[0] = address(eTSTsecondary); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - // assertEq(eulerAggregationLayer.totalAllocated(), eTSTsecondaryMaxDeposit); + // assertEq(eulerAggregationVault.totalAllocated(), eTSTsecondaryMaxDeposit); assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationLayer))), + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), expectedStrategyCash ); assertEq( - (eulerAggregationLayer.getStrategy(address(eTSTsecondary))).allocated, + (eulerAggregationVault.getStrategy(address(eTSTsecondary))).allocated, strategyBefore.allocated + expectedStrategyCash ); } @@ -195,42 +195,42 @@ contract RebalanceTest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } // rebalance into strategy vm.warp(block.timestamp + 86400); - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); uint256 eTSTMaxDeposit = 0; // mock max deposit vm.mockCall( - address(eTST), abi.encodeCall(eTST.maxDeposit, (address(eulerAggregationLayer))), abi.encode(eTSTMaxDeposit) + address(eTST), abi.encodeCall(eTST.maxDeposit, (address(eulerAggregationVault))), abi.encode(eTSTMaxDeposit) ); vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - assertEq(eulerAggregationLayer.totalAllocated(), strategyBefore.allocated); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); - assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated); + assertEq(eulerAggregationVault.totalAllocated(), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated); } function testRebalanceByWithdrawing() public { @@ -238,19 +238,19 @@ contract RebalanceTest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -259,30 +259,30 @@ contract RebalanceTest is EulerAggregationLayerBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; vm.prank(manager); - eulerAggregationLayer.adjustAllocationPoints(address(eTST), newAllocationPoints); + eulerAggregationVault.adjustAllocationPoints(address(eTST), newAllocationPoints); vm.warp(block.timestamp + 86400); - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() * strategyBefore.allocationPoints - / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() * strategyBefore.allocationPoints + / eulerAggregationVault.totalAllocationPoints(); vm.prank(user1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - assertEq(eulerAggregationLayer.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), expectedStrategyCash); + assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); assertEq( - (eulerAggregationLayer.getStrategy(address(eTST))).allocated, + (eulerAggregationVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated - (strategyBefore.allocated - expectedStrategyCash) ); } @@ -292,19 +292,19 @@ contract RebalanceTest is EulerAggregationLayerBase { // deposit into aggregator { - uint256 balanceBefore = eulerAggregationLayer.balanceOf(user1); - uint256 totalSupplyBefore = eulerAggregationLayer.totalSupply(); - uint256 totalAssetsDepositedBefore = eulerAggregationLayer.totalAssetsDeposited(); + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); vm.startPrank(user1); - assetTST.approve(address(eulerAggregationLayer), amountToDeposit); - eulerAggregationLayer.deposit(amountToDeposit, user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); vm.stopPrank(); - assertEq(eulerAggregationLayer.balanceOf(user1), balanceBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalSupply(), totalSupplyBefore + amountToDeposit); - assertEq(eulerAggregationLayer.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); } @@ -313,45 +313,45 @@ contract RebalanceTest is EulerAggregationLayerBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; vm.prank(manager); - eulerAggregationLayer.adjustAllocationPoints(address(eTST), newAllocationPoints); + eulerAggregationVault.adjustAllocationPoints(address(eTST), newAllocationPoints); vm.warp(block.timestamp + 86400); - IEulerAggregationLayer.Strategy memory strategyBefore = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationLayer.totalAssetsAllocatable() * strategyBefore.allocationPoints - / eulerAggregationLayer.totalAllocationPoints(); + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() * strategyBefore.allocationPoints + / eulerAggregationVault.totalAllocationPoints(); uint256 expectedToWithdraw = strategyBefore.allocated - expectedStrategyCash; uint256 eTSTMaxWithdraw = expectedToWithdraw * 7e17 / 1e18; // mock max withdraw vm.mockCall( address(eTST), - abi.encodeCall(eTST.maxWithdraw, (address(eulerAggregationLayer))), + abi.encodeCall(eTST.maxWithdraw, (address(eulerAggregationVault))), abi.encode(eTSTMaxWithdraw) ); vm.prank(user1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationLayer), strategiesToRebalance); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); - // assertEq(eulerAggregationLayer.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); + // assertEq(eulerAggregationVault.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); // assertEq( - // eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationLayer))), strategyBefore.allocated - eTSTMaxWithdraw + // eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated - eTSTMaxWithdraw // ); - // assertEq((eulerAggregationLayer.getStrategy(address(eTST))).allocated, strategyBefore.allocated - eTSTMaxWithdraw); + // assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated - eTSTMaxWithdraw); } function testRebalanceFromRandomSender() public { vm.startPrank(user1); vm.expectRevert(ErrorsLib.NotRebalancer.selector); - eulerAggregationLayer.rebalance(address(eTST), 1, true); + eulerAggregationVault.rebalance(address(eTST), 1, true); vm.stopPrank(); } } diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index 6dfb15ab..bcc3e46e 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, IEVault, - IEulerAggregationLayer -} from "../common/EulerAggregationLayerBase.t.sol"; + IEulerAggregationVault +} from "../common/EulerAggregationVaultBase.t.sol"; -contract RemoveStrategyTest is EulerAggregationLayerBase { +contract RemoveStrategyTest is EulerAggregationVaultBase { uint256 strategyAllocationPoints; IEVault anotherStrategy; @@ -21,17 +21,17 @@ contract RemoveStrategyTest is EulerAggregationLayerBase { } function testRemoveStrategy() public { - uint256 totalAllocationPointsBefore = eulerAggregationLayer.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = eulerAggregationVault.totalAllocationPoints(); uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); - eulerAggregationLayer.removeStrategy(address(eTST)); + eulerAggregationVault.removeStrategy(address(eTST)); - IEulerAggregationLayer.Strategy memory strategyAfter = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyAfter = eulerAggregationVault.getStrategy(address(eTST)); assertEq(strategyAfter.active, false); assertEq(strategyAfter.allocationPoints, 0); - assertEq(eulerAggregationLayer.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); + assertEq(eulerAggregationVault.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore - 1); } @@ -41,17 +41,17 @@ contract RemoveStrategyTest is EulerAggregationLayerBase { ); _addStrategy(manager, address(anotherStrategy), strategyAllocationPoints); - uint256 totalAllocationPointsBefore = eulerAggregationLayer.totalAllocationPoints(); + uint256 totalAllocationPointsBefore = eulerAggregationVault.totalAllocationPoints(); uint256 withdrawalQueueLengthBefore = _getWithdrawalQueueLength(); vm.prank(manager); - eulerAggregationLayer.removeStrategy(address(eTST)); + eulerAggregationVault.removeStrategy(address(eTST)); - IEulerAggregationLayer.Strategy memory strategyAfter = eulerAggregationLayer.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory strategyAfter = eulerAggregationVault.getStrategy(address(eTST)); assertEq(strategyAfter.active, false); assertEq(strategyAfter.allocationPoints, 0); - assertEq(eulerAggregationLayer.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); + assertEq(eulerAggregationVault.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore - 1); } @@ -75,7 +75,7 @@ contract RemoveStrategyTest is EulerAggregationLayerBase { assertEq(withdrawalQueue[3], strategy3); vm.prank(manager); - eulerAggregationLayer.removeStrategy(strategy2); + eulerAggregationVault.removeStrategy(strategy2); withdrawalQueue = _getWithdrawalQueue(); assertEq(withdrawalQueue.length, 3); @@ -84,7 +84,7 @@ contract RemoveStrategyTest is EulerAggregationLayerBase { assertEq(withdrawalQueue[2], strategy3); vm.prank(manager); - eulerAggregationLayer.removeStrategy(strategy3); + eulerAggregationVault.removeStrategy(strategy3); withdrawalQueue = _getWithdrawalQueue(); assertEq(withdrawalQueue.length, 2); @@ -92,14 +92,14 @@ contract RemoveStrategyTest is EulerAggregationLayerBase { assertEq(withdrawalQueue[1], strategy1); vm.prank(manager); - eulerAggregationLayer.removeStrategy(address(eTST)); + eulerAggregationVault.removeStrategy(address(eTST)); withdrawalQueue = _getWithdrawalQueue(); assertEq(withdrawalQueue.length, 1); assertEq(withdrawalQueue[0], strategy1); vm.prank(manager); - eulerAggregationLayer.removeStrategy(strategy1); + eulerAggregationVault.removeStrategy(strategy1); withdrawalQueue = _getWithdrawalQueue(); assertEq(withdrawalQueue.length, 0); @@ -108,12 +108,12 @@ contract RemoveStrategyTest is EulerAggregationLayerBase { function testRemoveStrategy_fromUnauthorized() public { vm.prank(deployer); vm.expectRevert(); - eulerAggregationLayer.removeStrategy(address(eTST)); + eulerAggregationVault.removeStrategy(address(eTST)); } function testRemoveStrategy_AlreadyRemoved() public { vm.prank(manager); vm.expectRevert(); - eulerAggregationLayer.removeStrategy(address(eTST2)); + eulerAggregationVault.removeStrategy(address(eTST2)); } } diff --git a/test/unit/ReorderWithdrawalQueueTest.t.sol b/test/unit/ReorderWithdrawalQueueTest.t.sol index 5f684df6..722dc930 100644 --- a/test/unit/ReorderWithdrawalQueueTest.t.sol +++ b/test/unit/ReorderWithdrawalQueueTest.t.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.0; import { - EulerAggregationLayerBase, - EulerAggregationLayer, + EulerAggregationVaultBase, + EulerAggregationVault, IEVault, WithdrawalQueue -} from "../common/EulerAggregationLayerBase.t.sol"; +} from "../common/EulerAggregationVaultBase.t.sol"; -contract ReorderWithdrawalQueueTest is EulerAggregationLayerBase { +contract ReorderWithdrawalQueueTest is EulerAggregationVaultBase { uint256 eTSTAllocationPoints = 500e18; uint256 eTSTsecondaryAllocationPoints = 700e18; @@ -30,18 +30,18 @@ contract ReorderWithdrawalQueueTest is EulerAggregationLayerBase { } function testReorderWithdrawalQueue() public { - assertEq(eulerAggregationLayer.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTAllocationPoints); + assertEq(eulerAggregationVault.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTAllocationPoints); assertEq( - eulerAggregationLayer.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTsecondaryAllocationPoints + eulerAggregationVault.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTsecondaryAllocationPoints ); vm.prank(manager); withdrawalQueue.reorderWithdrawalQueue(0, 1); assertEq( - eulerAggregationLayer.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTsecondaryAllocationPoints + eulerAggregationVault.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTsecondaryAllocationPoints ); - assertEq(eulerAggregationLayer.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTAllocationPoints); + assertEq(eulerAggregationVault.getStrategy(_getWithdrawalQueue()[1]).allocationPoints, eTSTAllocationPoints); } function testReorderWithdrawalQueueWhenOutOfBounds() public { diff --git a/test/unit/SetRebalancerTest.t.sol b/test/unit/SetRebalancerTest.t.sol index 6073582d..5199c661 100644 --- a/test/unit/SetRebalancerTest.t.sol +++ b/test/unit/SetRebalancerTest.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {EulerAggregationLayerBase, EulerAggregationLayer, ErrorsLib} from "../common/EulerAggregationLayerBase.t.sol"; +import {EulerAggregationVaultBase, EulerAggregationVault, ErrorsLib} from "../common/EulerAggregationVaultBase.t.sol"; -contract SetRebalancerTest is EulerAggregationLayerBase { +contract SetRebalancerTest is EulerAggregationVaultBase { function setUp() public virtual override { super.setUp(); } @@ -11,15 +11,15 @@ contract SetRebalancerTest is EulerAggregationLayerBase { function testSetInvalidRebalancer() public { vm.startPrank(manager); vm.expectRevert(ErrorsLib.InvalidPlugin.selector); - eulerAggregationLayer.setRebalancer(address(0)); + eulerAggregationVault.setRebalancer(address(0)); } function testSetRebalancer() public { address newRebalancer = makeAddr("Rebalancer"); vm.prank(manager); - eulerAggregationLayer.setRebalancer(newRebalancer); + eulerAggregationVault.setRebalancer(newRebalancer); - assertEq(eulerAggregationLayer.rebalancer(), newRebalancer); + assertEq(eulerAggregationVault.rebalancer(), newRebalancer); } } diff --git a/test/unit/SetWithdrawalQueueTest.t.sol b/test/unit/SetWithdrawalQueueTest.t.sol index f0011142..e41ae03e 100644 --- a/test/unit/SetWithdrawalQueueTest.t.sol +++ b/test/unit/SetWithdrawalQueueTest.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {EulerAggregationLayerBase, EulerAggregationLayer, ErrorsLib} from "../common/EulerAggregationLayerBase.t.sol"; +import {EulerAggregationVaultBase, EulerAggregationVault, ErrorsLib} from "../common/EulerAggregationVaultBase.t.sol"; -contract SetWithdrawalQueueTest is EulerAggregationLayerBase { +contract SetWithdrawalQueueTest is EulerAggregationVaultBase { function setUp() public virtual override { super.setUp(); } @@ -11,15 +11,15 @@ contract SetWithdrawalQueueTest is EulerAggregationLayerBase { function testSetInvalidWithdrawalQueue() public { vm.startPrank(manager); vm.expectRevert(ErrorsLib.InvalidPlugin.selector); - eulerAggregationLayer.setWithdrawalQueue(address(0)); + eulerAggregationVault.setWithdrawalQueue(address(0)); } function testSetWithdrawalQueue() public { address newWithdrawalQueue = makeAddr("WITHDRAWAL_QUEUE"); vm.prank(manager); - eulerAggregationLayer.setWithdrawalQueue(newWithdrawalQueue); + eulerAggregationVault.setWithdrawalQueue(newWithdrawalQueue); - assertEq(eulerAggregationLayer.withdrawalQueue(), newWithdrawalQueue); + assertEq(eulerAggregationVault.withdrawalQueue(), newWithdrawalQueue); } } From 0994bab0fe490291b532250c4ef3b58b692e4eae Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 5 Jul 2024 12:30:58 +0300 Subject: [PATCH 230/316] gulp only if min amount is minted --- src/core/EulerAggregationVault.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index d5988d47..a2d42395 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -50,8 +50,10 @@ contract EulerAggregationVault is bytes32 public constant AGGREGATION_LAYER_MANAGER = keccak256("AGGREGATION_LAYER_MANAGER"); bytes32 public constant AGGREGATION_LAYER_MANAGER_ADMIN = keccak256("AGGREGATION_LAYER_MANAGER_ADMIN"); - /// @dev interest rate smearing period + /// @dev Interest rate smearing period uint256 public constant INTEREST_SMEAR = 2 weeks; + /// @dev Minimum amount of shares to exist for gulp to be enabled + uint256 public constant MIN_SHARES_FOR_GULP = 1e7; constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) Dispatch(_rewardsModule, _hooksModule, _feeModule, _allocationPointsModule) @@ -475,7 +477,8 @@ contract EulerAggregationVault is AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - if (totalSupply() == 0) return; + // Do not gulp if total supply is too low + if (totalSupply() < MIN_SHARES_FOR_GULP) return; uint256 toGulp = totalAssetsAllocatable() - $.totalAssetsDeposited - $.interestLeft; if (toGulp == 0) return; From 6b7546fa166110650a86310357d5a28db187920a Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 5 Jul 2024 13:22:05 +0300 Subject: [PATCH 231/316] add invariant --- test/invariant/EulerAggregationLayerInvariants.t.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 622bdcac..e907ae24 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -124,6 +124,13 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { } } + function invariant_interestLeft() public view { + EulerAggregationVault.AggregationVaultSavingRate memory aggregationVaultSavingRate = + eulerAggregationVault.getAggregationVaultSavingRate(); + uint256 accruedInterest = eulerAggregationVault.interestAccrued(); + assertGe(aggregationVaultSavingRate.interestLeft, accruedInterest); + } + function _deployOtherStrategies() private { eTSTsecond = IEVault( factory.createProxy(address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount)) From 4bda9baec46c32c5eed8c2d3bc4e2e605789142e Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 5 Jul 2024 13:22:17 +0300 Subject: [PATCH 232/316] gulp fuzzing --- test/fuzz/GulpFuzzTest.t.sol | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 test/fuzz/GulpFuzzTest.t.sol diff --git a/test/fuzz/GulpFuzzTest.t.sol b/test/fuzz/GulpFuzzTest.t.sol new file mode 100644 index 00000000..67756c56 --- /dev/null +++ b/test/fuzz/GulpFuzzTest.t.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + EulerAggregationVaultBase, + EulerAggregationVault, + IEulerAggregationVault +} from "../common/EulerAggregationVaultBase.t.sol"; + +contract GulpFuzzTest is EulerAggregationVaultBase { + function setUp() public virtual override { + super.setUp(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + } + + function testFuzz_interestAccrued_under_uint168( + uint256 _interestAmount, + uint256 _depositAmount, + uint256 _timePassed + ) public { + _depositAmount = bound(_depositAmount, 0, type(uint112).max); + // this makes sure that the mint won't cause overflow in token accounting + _interestAmount = bound(_interestAmount, 0, type(uint112).max - _depositAmount); + _timePassed = bound(_timePassed, block.timestamp, type(uint40).max); + + assetTST.mint(user1, _depositAmount); + vm.startPrank(user1); + assetTST.approve(address(eulerAggregationVault), _depositAmount); + eulerAggregationVault.deposit(_depositAmount, user1); + vm.stopPrank(); + + assetTST.mint(address(eulerAggregationVault), _interestAmount); + eulerAggregationVault.gulp(); + + vm.warp(_timePassed); + uint256 interestAccrued = eulerAggregationVault.interestAccrued(); + + assertLe(interestAccrued, type(uint168).max); + } + + // this tests shows that when you have a very small deposit and a very large interestAmount minted to the contract + function testFuzz_gulp_under_uint168(uint256 _interestAmount, uint256 _depositAmount) public { + _depositAmount = bound(_depositAmount, 1e7, type(uint112).max); + _interestAmount = bound(_interestAmount, 0, type(uint256).max - _depositAmount); // this makes sure that the mint won't cause overflow + + assetTST.mint(address(eulerAggregationVault), _interestAmount); + + assetTST.mint(user1, _depositAmount); + vm.startPrank(user1); + assetTST.approve(address(eulerAggregationVault), _depositAmount); + eulerAggregationVault.deposit(_depositAmount, user1); + vm.stopPrank(); + + eulerAggregationVault.gulp(); + + EulerAggregationVault.AggregationVaultSavingRate memory aggregationVaultSavingRate = + eulerAggregationVault.getAggregationVaultSavingRate(); + + if (_interestAmount <= type(uint168).max) { + assertEq(aggregationVaultSavingRate.interestLeft, _interestAmount); + } else { + assertEq(aggregationVaultSavingRate.interestLeft, type(uint168).max); + } + } +} From 85a99db7a41274fcd5ee5489c5cb3bcc4f28f394 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:17:26 +0300 Subject: [PATCH 233/316] test: gulp fuzzing --- test/fuzz/GulpFuzzTest.t.sol | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/test/fuzz/GulpFuzzTest.t.sol b/test/fuzz/GulpFuzzTest.t.sol index 67756c56..602eff00 100644 --- a/test/fuzz/GulpFuzzTest.t.sol +++ b/test/fuzz/GulpFuzzTest.t.sol @@ -1,11 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import { - EulerAggregationVaultBase, - EulerAggregationVault, - IEulerAggregationVault -} from "../common/EulerAggregationVaultBase.t.sol"; +import {EulerAggregationVaultBase, EulerAggregationVault} from "../common/EulerAggregationVaultBase.t.sol"; contract GulpFuzzTest is EulerAggregationVaultBase { function setUp() public virtual override { @@ -15,11 +11,9 @@ contract GulpFuzzTest is EulerAggregationVaultBase { _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); } - function testFuzz_interestAccrued_under_uint168( - uint256 _interestAmount, - uint256 _depositAmount, - uint256 _timePassed - ) public { + function testFuzzInterestAccruedUnderUint168(uint256 _interestAmount, uint256 _depositAmount, uint256 _timePassed) + public + { _depositAmount = bound(_depositAmount, 0, type(uint112).max); // this makes sure that the mint won't cause overflow in token accounting _interestAmount = bound(_interestAmount, 0, type(uint112).max - _depositAmount); @@ -41,7 +35,7 @@ contract GulpFuzzTest is EulerAggregationVaultBase { } // this tests shows that when you have a very small deposit and a very large interestAmount minted to the contract - function testFuzz_gulp_under_uint168(uint256 _interestAmount, uint256 _depositAmount) public { + function testFuzzGulpUnderUint168(uint256 _interestAmount, uint256 _depositAmount) public { _depositAmount = bound(_depositAmount, 1e7, type(uint112).max); _interestAmount = bound(_interestAmount, 0, type(uint256).max - _depositAmount); // this makes sure that the mint won't cause overflow @@ -64,4 +58,24 @@ contract GulpFuzzTest is EulerAggregationVaultBase { assertEq(aggregationVaultSavingRate.interestLeft, type(uint168).max); } } + + function testFuzzGulpBelowMinSharesForGulp() public { + uint256 depositAmount = 1337; + assetTST.mint(user1, depositAmount); + vm.startPrank(user1); + assetTST.approve(address(eulerAggregationVault), depositAmount); + eulerAggregationVault.deposit(depositAmount, user1); + vm.stopPrank(); + + uint256 interestAmount = 10e18; + // Mint interest directly into the contract + assetTST.mint(address(eulerAggregationVault), interestAmount); + eulerAggregationVault.gulp(); + skip(eulerAggregationVault.INTEREST_SMEAR()); + + EulerAggregationVault.AggregationVaultSavingRate memory aggregationVaultSavingRate = + eulerAggregationVault.getAggregationVaultSavingRate(); + assertEq(eulerAggregationVault.totalAssets(), depositAmount); + assertEq(aggregationVaultSavingRate.interestLeft, 0); + } } From 0d16a9826d1bb2865526ff3a815caacf250e8d63 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:54:00 +0300 Subject: [PATCH 234/316] add invariant and comments --- .../EulerAggregationLayerInvariants.t.sol | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index e907ae24..ac91e6a5 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -59,6 +59,7 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { targetContract(address(withdrawalQueueHandler)); } + // Right after gulp, total assets allocatable should be always equal to total assets deposited + interest left. function invariant_gulp() public { eulerAggregationVault.gulp(); @@ -69,6 +70,16 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { ); } + // totalAssetsDeposited should be equal to the totalAssetsAllocatable after SMEAR has passed. + function invariant_totalAssets() public { + eulerAggregationVault.gulp(); + skip(eulerAggregationVault.INTEREST_SMEAR()); // make sure smear has passed + eulerAggregationVault.updateInterestAccrued(); + + assertEq(eulerAggregationVault.totalAssets(), eulerAggregationVault.totalAssetsAllocatable()); + } + + // total allocation points should be equal to the sum of the allocation points of all strategies. function invariant_totalAllocationPoints() public view { address withdrawalQueueAddr = eulerAggregationVault.withdrawalQueue(); @@ -85,6 +96,8 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { assertEq(eulerAggregationVault.totalAllocationPoints(), expectedTotalAllocationpoints); } + // If `total allocation points - cash reserve allocation points == 0`(no strategy added), the withdrawal queue length should be zero. + // Else, the length should be greater than zero. function invariant_withdrawalQueue() public view { address withdrawalQueueAddr = eulerAggregationVault.withdrawalQueue(); @@ -99,6 +112,7 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { } } + // total allocated amount should always be equal the sum of allocated amount in all the strategies. function invariant_totalAllocated() public view { address withdrawalQueueAddr = eulerAggregationVault.withdrawalQueue(); @@ -113,6 +127,7 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { assertEq(eulerAggregationVault.totalAllocated(), aggregatedAllocatedAmount); } + // Balance of a certain fee recipient should always be equal to the ghost tracking variable. function invariant_performanceFee() public view { for (uint256 i; i < eulerAggregationVaultHandler.ghostFeeRecipientsLength(); i++) { address feeRecipient = eulerAggregationVaultHandler.ghost_feeRecipients(i); @@ -124,6 +139,7 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { } } + // the interest left should always be greater or equal current interest accrued value. function invariant_interestLeft() public view { EulerAggregationVault.AggregationVaultSavingRate memory aggregationVaultSavingRate = eulerAggregationVault.getAggregationVaultSavingRate(); From cb4951b3810c9121ec8595204b5b0dff6dbec893 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:14:27 +0300 Subject: [PATCH 235/316] feat: whitelisted withdrawal queue --- src/core/Dispatch.sol | 20 ++++--- src/core/EulerAggregationVault.sol | 59 ++++++++++--------- src/core/EulerAggregationVaultFactory.sol | 31 +++++++--- src/core/interface/IEulerAggregationVault.sol | 3 - .../IEulerAggregationVaultFactory.sol | 6 ++ src/core/module/Fee.sol | 4 +- test/common/EulerAggregationVaultBase.t.sol | 8 +-- .../handler/EulerAggregationVaultHandler.sol | 6 +- 8 files changed, 82 insertions(+), 55 deletions(-) create mode 100644 src/core/interface/IEulerAggregationVaultFactory.sol diff --git a/src/core/Dispatch.sol b/src/core/Dispatch.sol index 7d79cda5..408c31ce 100644 --- a/src/core/Dispatch.sol +++ b/src/core/Dispatch.sol @@ -5,17 +5,19 @@ pragma solidity ^0.8.0; import {Shared} from "./common/Shared.sol"; import {HooksModule} from "./module/Hooks.sol"; import {RewardsModule} from "./module/Rewards.sol"; +import {AllocationPointsModule} from "./module/AllocationPoints.sol"; +import {FeeModule} from "./module/Fee.sol"; /// @title Dispatch contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) /// @dev This contract implement the modifier to use for forwarding calls to a specific module using delegateCall. /// @dev Copied from https://github.com/euler-xyz/euler-vault-kit/blob/55d1a1fd7d572372f1c8b9f58aba0604bda3ca4f/src/EVault/Dispatch.sol. -abstract contract Dispatch is RewardsModule, HooksModule { - address public immutable MODULE_REWARDS; - address public immutable MODULE_HOOKS; - address public immutable MODULE_FEE; - address public immutable MODULE_ALLOCATION_POINTS; +abstract contract Dispatch is RewardsModule, HooksModule, FeeModule, AllocationPointsModule { + address public immutable rewardsModule; + address public immutable hooksModule; + address public immutable feeModule; + address public immutable allocationPointsModule; /// @dev Constructor. /// @param _rewardsModule Address of Rewards module. @@ -23,10 +25,10 @@ abstract contract Dispatch is RewardsModule, HooksModule { /// @param _feeModule Address of Fee module. /// @param _allocationPointsModule Address of AllocationPoints module. constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) { - MODULE_REWARDS = _rewardsModule; - MODULE_HOOKS = _hooksModule; - MODULE_FEE = _feeModule; - MODULE_ALLOCATION_POINTS = _allocationPointsModule; + rewardsModule = _rewardsModule; + hooksModule = _hooksModule; + feeModule = _feeModule; + allocationPointsModule = _allocationPointsModule; } // Modifier proxies the function call to a module and low-level returns the result diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index a2d42395..98333225 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -55,6 +55,11 @@ contract EulerAggregationVault is /// @dev Minimum amount of shares to exist for gulp to be enabled uint256 public constant MIN_SHARES_FOR_GULP = 1e7; + /// @dev Constructor. + /// @param _rewardsModule Address of Rewards module. + /// @param _hooksModule Address of Hooks module. + /// @param _feeModule Address of Fee module. + /// @param _allocationPointsModule Address of AllocationPoints module. constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) Dispatch(_rewardsModule, _hooksModule, _feeModule, _allocationPointsModule) {} @@ -92,17 +97,22 @@ contract EulerAggregationVault is } /// @dev See {FeeModule-setFeeRecipient}. - function setFeeRecipient(address _newFeeRecipient) external onlyRole(AGGREGATION_LAYER_MANAGER) use(MODULE_FEE) {} + function setFeeRecipient(address _newFeeRecipient) + external + override + onlyRole(AGGREGATION_LAYER_MANAGER) + use(feeModule) + {} /// @dev See {FeeModule-setPerformanceFee}. - function setPerformanceFee(uint256 _newFee) external onlyRole(AGGREGATION_LAYER_MANAGER) use(MODULE_FEE) {} + function setPerformanceFee(uint256 _newFee) external override onlyRole(AGGREGATION_LAYER_MANAGER) use(feeModule) {} /// @dev See {RewardsModule-optInStrategyRewards}. function optInStrategyRewards(address _strategy) external override onlyRole(AGGREGATION_LAYER_MANAGER) - use(MODULE_REWARDS) + use(rewardsModule) {} /// @dev See {RewardsModule-optOutStrategyRewards}. @@ -110,7 +120,7 @@ contract EulerAggregationVault is external override onlyRole(AGGREGATION_LAYER_MANAGER) - use(MODULE_REWARDS) + use(rewardsModule) {} /// @dev See {RewardsModule-optOutStrategyRewards}. @@ -118,7 +128,7 @@ contract EulerAggregationVault is external override onlyRole(AGGREGATION_LAYER_MANAGER) - use(MODULE_REWARDS) + use(rewardsModule) {} /// @dev See {RewardsModule-disableRewardForStrategy}. @@ -126,7 +136,7 @@ contract EulerAggregationVault is external override onlyRole(AGGREGATION_LAYER_MANAGER) - use(MODULE_REWARDS) + use(rewardsModule) {} /// @dev See {RewardsModule-claimStrategyReward}. @@ -134,7 +144,7 @@ contract EulerAggregationVault is external override onlyRole(AGGREGATION_LAYER_MANAGER) - use(MODULE_REWARDS) + use(rewardsModule) {} /// @dev See {HooksModule-setHooksConfig}. @@ -142,51 +152,46 @@ contract EulerAggregationVault is external override onlyRole(AGGREGATION_LAYER_MANAGER) - use(MODULE_HOOKS) + use(hooksModule) {} /// @dev See {AllocationPointsModule-adjustAllocationPoints}. function adjustAllocationPoints(address _strategy, uint256 _newPoints) external - use(MODULE_ALLOCATION_POINTS) + override + use(allocationPointsModule) onlyRole(ALLOCATIONS_MANAGER) {} /// @dev See {AllocationPointsModule-setStrategyCap}. function setStrategyCap(address _strategy, uint256 _cap) external - use(MODULE_ALLOCATION_POINTS) + override + use(allocationPointsModule) onlyRole(ALLOCATIONS_MANAGER) {} /// @dev See {AllocationPointsModule-addStrategy}. function addStrategy(address _strategy, uint256 _allocationPoints) external - use(MODULE_ALLOCATION_POINTS) + override + use(allocationPointsModule) onlyRole(STRATEGY_ADDER) {} /// @dev See {AllocationPointsModule-removeStrategy}. - function removeStrategy(address _strategy) external use(MODULE_ALLOCATION_POINTS) onlyRole(STRATEGY_REMOVER) {} + function removeStrategy(address _strategy) + external + override + use(allocationPointsModule) + onlyRole(STRATEGY_REMOVER) + {} /// @dev See {RewardsModule-enableBalanceForwarder}. - function enableBalanceForwarder() external override use(MODULE_REWARDS) {} + function enableBalanceForwarder() external override use(rewardsModule) {} /// @dev See {RewardsModule-disableBalanceForwarder}. - function disableBalanceForwarder() external override use(MODULE_REWARDS) {} - - /// @notice Set a new address for WithdrawalQueue plugin. - /// @dev Can only be called by an address with the `AGGREGATION_LAYER_MANAGER` role. - /// @param _withdrawalQueue New WithdrawalQueue contract address. - function setWithdrawalQueue(address _withdrawalQueue) external onlyRole(AGGREGATION_LAYER_MANAGER) { - if (_withdrawalQueue == address(0)) revert Errors.InvalidPlugin(); - - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - emit Events.SetWithdrawalQueue($.withdrawalQueue, _withdrawalQueue); - - $.withdrawalQueue = _withdrawalQueue; - } + function disableBalanceForwarder() external override use(rewardsModule) {} /// @notice Set a new address for Rebalancer plugin. /// @dev Can only be called by an address with the `AGGREGATION_LAYER_MANAGER` role. diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index 04b6c7b2..65491f00 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -7,13 +7,18 @@ import {Hooks} from "./module/Hooks.sol"; import {Fee} from "./module/Fee.sol"; import {WithdrawalQueue} from "../plugin/WithdrawalQueue.sol"; import {EulerAggregationVault, IEulerAggregationVault} from "./EulerAggregationVault.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + // libs import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; /// @title EulerAggregationVaultFactory contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) -contract EulerAggregationVaultFactory { +contract EulerAggregationVaultFactory is Ownable { + error WithdrawalQueueAlreadyWhitelisted(); + error NotWhitelistedWithdrawalQueueImpl(); + /// core dependencies address public immutable balanceTracker; /// core modules implementations addresses @@ -24,8 +29,10 @@ contract EulerAggregationVaultFactory { /// plugins /// @dev Rebalancer plugin contract address, one instance can serve different aggregation vaults address public immutable rebalancer; - /// @dev WithdrawalQueue plugin implementation address, need to be deployed per aggregation vault - address public immutable withdrawalQueueImpl; + /// @dev Mapping to set whitelisted WhithdrawalQueue plugin implementation. + mapping(address => bool) public isWhitelistedWithdrawalQueueImpl; + /// @dev An array of the whitelisted WithdrawalQueue plugin implementations. + address[] public withdrawalQueueImpls; /// @dev Init params struct. struct FactoryParams { @@ -35,12 +42,12 @@ contract EulerAggregationVaultFactory { address feeModuleImpl; address allocationPointsModuleImpl; address rebalancer; - address withdrawalQueueImpl; } /// @notice Constructor. + /// @param _owner Factory owner. /// @param _factoryParams FactoryParams struct. - constructor(FactoryParams memory _factoryParams) { + constructor(address _owner, FactoryParams memory _factoryParams) Ownable(_owner) { balanceTracker = _factoryParams.balanceTracker; rewardsModuleImpl = _factoryParams.rewardsModuleImpl; hooksModuleImpl = _factoryParams.hooksModuleImpl; @@ -48,7 +55,14 @@ contract EulerAggregationVaultFactory { allocationPointsModuleImpl = _factoryParams.allocationPointsModuleImpl; rebalancer = _factoryParams.rebalancer; - withdrawalQueueImpl = _factoryParams.withdrawalQueueImpl; + } + + function whitelistWithdrawalQueueImpl(address _withdrawalQueuImpl) external onlyOwner { + if (isWhitelistedWithdrawalQueueImpl[_withdrawalQueuImpl]) revert WithdrawalQueueAlreadyWhitelisted(); + + isWhitelistedWithdrawalQueueImpl[_withdrawalQueuImpl] = true; + + withdrawalQueueImpls.push(_withdrawalQueuImpl); } /// @notice Deploy a new aggregation layer vault. @@ -61,11 +75,14 @@ contract EulerAggregationVaultFactory { /// @param _initialCashAllocationPoints The amount of points to initally allocate for cash reserve. /// @return eulerAggregationVault The address of the new deployed aggregation layer vault. function deployEulerAggregationVault( + address _withdrawalQueueImpl, address _asset, string memory _name, string memory _symbol, uint256 _initialCashAllocationPoints ) external returns (address) { + if (!isWhitelistedWithdrawalQueueImpl[_withdrawalQueueImpl]) revert NotWhitelistedWithdrawalQueueImpl(); + // cloning core modules address rewardsModuleAddr = Clones.clone(rewardsModuleImpl); address hooksModuleAddr = Clones.clone(hooksModuleImpl); @@ -73,7 +90,7 @@ contract EulerAggregationVaultFactory { address allocationpointsModuleAddr = Clones.clone(allocationPointsModuleImpl); // cloning plugins - WithdrawalQueue withdrawalQueue = WithdrawalQueue(Clones.clone(withdrawalQueueImpl)); + WithdrawalQueue withdrawalQueue = WithdrawalQueue(Clones.clone(_withdrawalQueueImpl)); // deploy new aggregation vault EulerAggregationVault eulerAggregationVault = diff --git a/src/core/interface/IEulerAggregationVault.sol b/src/core/interface/IEulerAggregationVault.sol index 6d71d4a9..31074c85 100644 --- a/src/core/interface/IEulerAggregationVault.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -50,9 +50,6 @@ interface IEulerAggregationVault { uint256 assets, uint256 shares ) external; - function adjustAllocationPoints(address _strategy, uint256 _newPoints) external; - function addStrategy(address _strategy, uint256 _allocationPoints) external; - function removeStrategy(address _strategy) external; function getStrategy(address _strategy) external view returns (Strategy memory); function totalAllocationPoints() external view returns (uint256); function totalAllocated() external view returns (uint256); diff --git a/src/core/interface/IEulerAggregationVaultFactory.sol b/src/core/interface/IEulerAggregationVaultFactory.sol new file mode 100644 index 00000000..20b6392c --- /dev/null +++ b/src/core/interface/IEulerAggregationVaultFactory.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +interface IEulerAggregationVaultFactory { + function isWhitelistedWithdrawalQueueImpl(address _withdrawalQueueImpl) external view returns (bool); +} diff --git a/src/core/module/Fee.sol b/src/core/module/Fee.sol index b013d682..a24d2af7 100644 --- a/src/core/module/Fee.sol +++ b/src/core/module/Fee.sol @@ -20,7 +20,7 @@ abstract contract FeeModule { /// @notice Set performance fee recipient address /// @param _newFeeRecipient Recipient address - function setFeeRecipient(address _newFeeRecipient) external { + function setFeeRecipient(address _newFeeRecipient) external virtual { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); address feeRecipientCached = $.feeRecipient; @@ -33,7 +33,7 @@ abstract contract FeeModule { /// @notice Set performance fee (1e18 == 100%) /// @param _newFee Fee rate - function setPerformanceFee(uint256 _newFee) external { + function setPerformanceFee(uint256 _newFee) external virtual { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); uint256 performanceFeeCached = $.performanceFee; diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index 46dc89c6..dc8b013b 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -91,10 +91,10 @@ contract EulerAggregationVaultBase is EVaultTestBase { vm.label(address(eulerAggregationVaultFactory), "eulerAggregationVaultFactory"); vm.label(address(eulerAggregationVault), "eulerAggregationVault"); - vm.label(eulerAggregationVault.MODULE_REWARDS(), "MODULE_REWARDS"); - vm.label(eulerAggregationVault.MODULE_HOOKS(), "MODULE_HOOKS"); - vm.label(eulerAggregationVault.MODULE_FEE(), "MODULE_FEE"); - vm.label(eulerAggregationVault.MODULE_ALLOCATION_POINTS(), "MODULE_ALLOCATION_POINTS"); + vm.label(eulerAggregationVault.rewardsModule(), "rewardsModule"); + vm.label(eulerAggregationVault.hooksModule(), "hooksModule"); + vm.label(eulerAggregationVault.feeModule(), "feeModule"); + vm.label(eulerAggregationVault.allocationPointsModule(), "allocationPointsModule"); vm.label(address(assetTST), "assetTST"); } diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index 471256be..134252ee 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -89,7 +89,7 @@ contract EulerAggregationVaultHandler is Test { (currentActor, success, returnData) = actorUtil.initiateExactActorCall( 0, address(eulerAggVault), - abi.encodeWithSelector(IEulerAggregationVault.adjustAllocationPoints.selector, strategyAddr, _newPoints) + abi.encodeWithSelector(EulerAggregationVault.adjustAllocationPoints.selector, strategyAddr, _newPoints) ); if (success) { @@ -126,7 +126,7 @@ contract EulerAggregationVaultHandler is Test { (currentActor, success, returnData) = actorUtil.initiateExactActorCall( 0, address(eulerAggVault), - abi.encodeWithSelector(IEulerAggregationVault.addStrategy.selector, strategyAddr, _allocationPoints) + abi.encodeWithSelector(EulerAggregationVault.addStrategy.selector, strategyAddr, _allocationPoints) ); if (success) { @@ -146,7 +146,7 @@ contract EulerAggregationVaultHandler is Test { (currentActor, success, returnData) = actorUtil.initiateExactActorCall( 0, address(eulerAggVault), - abi.encodeWithSelector(IEulerAggregationVault.removeStrategy.selector, strategyAddr) + abi.encodeWithSelector(EulerAggregationVault.removeStrategy.selector, strategyAddr) ); if (success) { From 298c6ab60049f1d88d2d9692b6e0f873fbdeaf80 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:40:46 +0300 Subject: [PATCH 236/316] fix --- test/A16zPropertyTests.t.sol | 13 ++++++---- test/common/EulerAggregationVaultBase.t.sol | 12 ++++++---- test/e2e/BalanceForwarderE2ETest.t.sol | 13 ++++++---- test/echidna/CryticERC4626TestsHarness.t.sol | 10 ++++---- test/unit/SetWithdrawalQueueTest.t.sol | 25 -------------------- 5 files changed, 32 insertions(+), 41 deletions(-) delete mode 100644 test/unit/SetWithdrawalQueueTest.t.sol diff --git a/test/A16zPropertyTests.t.sol b/test/A16zPropertyTests.t.sol index 1e487aaf..b73c0eb7 100644 --- a/test/A16zPropertyTests.t.sol +++ b/test/A16zPropertyTests.t.sol @@ -18,6 +18,8 @@ import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol"; contract A16zPropertyTests is ERC4626Test { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; + address public factoryOwner; + // core modules Rewards rewardsImpl; Hooks hooksImpl; @@ -31,6 +33,8 @@ contract A16zPropertyTests is ERC4626Test { EulerAggregationVault eulerAggregationVault; function setUp() public override { + factoryOwner = makeAddr("FACTORY_OWNER"); + rewardsImpl = new Rewards(); hooksImpl = new Hooks(); feeModuleImpl = new Fee(); @@ -45,14 +49,15 @@ contract A16zPropertyTests is ERC4626Test { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), allocationPointsModuleImpl: address(allocationPointsModuleImpl), - rebalancer: address(rebalancerPlugin), - withdrawalQueueImpl: address(withdrawalQueuePluginImpl) + rebalancer: address(rebalancerPlugin) }); - eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); + eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryOwner, factoryParams); + vm.prank(factoryOwner); + eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueuePluginImpl)); _underlying_ = address(new ERC20Mock()); _vault_ = eulerAggregationVaultFactory.deployEulerAggregationVault( - _underlying_, "E20M_Agg", "E20M_Agg", CASH_RESERVE_ALLOCATION_POINTS + address(withdrawalQueuePluginImpl), _underlying_, "E20M_Agg", "E20M_Agg", CASH_RESERVE_ALLOCATION_POINTS ); _delta_ = 0; _vaultMayBeEmpty = false; diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index dc8b013b..73c6eee4 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -61,14 +61,18 @@ contract EulerAggregationVaultBase is EVaultTestBase { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), allocationPointsModuleImpl: address(allocationPointsModuleImpl), - rebalancer: address(rebalancer), - withdrawalQueueImpl: address(withdrawalQueueImpl) + rebalancer: address(rebalancer) }); - eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); + eulerAggregationVaultFactory = new EulerAggregationVaultFactory(deployer, factoryParams); + eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueueImpl)); eulerAggregationVault = EulerAggregationVault( eulerAggregationVaultFactory.deployEulerAggregationVault( - address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS + address(withdrawalQueueImpl), + address(assetTST), + "assetTST_Agg", + "assetTST_Agg", + CASH_RESERVE_ALLOCATION_POINTS ) ); withdrawalQueue = WithdrawalQueue(eulerAggregationVault.withdrawalQueue()); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 5c7f91e9..3432d4d6 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -31,13 +31,18 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), allocationPointsModuleImpl: address(allocationPointsModuleImpl), - rebalancer: address(rebalancer), - withdrawalQueueImpl: address(withdrawalQueueImpl) + rebalancer: address(rebalancer) }); - eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); + eulerAggregationVaultFactory = new EulerAggregationVaultFactory(deployer, factoryParams); + eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueueImpl)); + eulerAggregationVault = EulerAggregationVault( eulerAggregationVaultFactory.deployEulerAggregationVault( - address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS + address(withdrawalQueueImpl), + address(assetTST), + "assetTST_Agg", + "assetTST_Agg", + CASH_RESERVE_ALLOCATION_POINTS ) ); diff --git a/test/echidna/CryticERC4626TestsHarness.t.sol b/test/echidna/CryticERC4626TestsHarness.t.sol index 62b46891..c645495c 100644 --- a/test/echidna/CryticERC4626TestsHarness.t.sol +++ b/test/echidna/CryticERC4626TestsHarness.t.sol @@ -17,6 +17,8 @@ import {TestERC20Token} from "crytic-properties/ERC4626/util/TestERC20Token.sol" contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; + address factoryDeployer; + // core modules Rewards rewardsImpl; Hooks hooksImpl; @@ -44,14 +46,14 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), allocationPointsModuleImpl: address(allocationPointsModuleImpl), - rebalancer: address(rebalancerPlugin), - withdrawalQueueImpl: address(withdrawalQueuePluginImpl) + rebalancer: address(rebalancerPlugin) }); - eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); + eulerAggregationVaultFactory = new EulerAggregationVaultFactory(address(this), factoryParams); + eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueuePluginImpl)); TestERC20Token _asset = new TestERC20Token("Test Token", "TT", 18); address _vault = eulerAggregationVaultFactory.deployEulerAggregationVault( - address(_asset), "TT_Agg", "TT_Agg", CASH_RESERVE_ALLOCATION_POINTS + address(withdrawalQueuePluginImpl), address(_asset), "TT_Agg", "TT_Agg", CASH_RESERVE_ALLOCATION_POINTS ); initialize(address(_vault), address(_asset), false); diff --git a/test/unit/SetWithdrawalQueueTest.t.sol b/test/unit/SetWithdrawalQueueTest.t.sol deleted file mode 100644 index e41ae03e..00000000 --- a/test/unit/SetWithdrawalQueueTest.t.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {EulerAggregationVaultBase, EulerAggregationVault, ErrorsLib} from "../common/EulerAggregationVaultBase.t.sol"; - -contract SetWithdrawalQueueTest is EulerAggregationVaultBase { - function setUp() public virtual override { - super.setUp(); - } - - function testSetInvalidWithdrawalQueue() public { - vm.startPrank(manager); - vm.expectRevert(ErrorsLib.InvalidPlugin.selector); - eulerAggregationVault.setWithdrawalQueue(address(0)); - } - - function testSetWithdrawalQueue() public { - address newWithdrawalQueue = makeAddr("WITHDRAWAL_QUEUE"); - - vm.prank(manager); - eulerAggregationVault.setWithdrawalQueue(newWithdrawalQueue); - - assertEq(eulerAggregationVault.withdrawalQueue(), newWithdrawalQueue); - } -} From 79788a791eda89043a49a00009bbb865f563a91c Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:16:31 +0300 Subject: [PATCH 237/316] clean; add events --- src/core/EulerAggregationVaultFactory.sol | 59 +++++++++++++++++++++++ src/core/lib/HooksLib.sol | 5 -- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index 65491f00..643de6b9 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -18,6 +18,7 @@ import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; contract EulerAggregationVaultFactory is Ownable { error WithdrawalQueueAlreadyWhitelisted(); error NotWhitelistedWithdrawalQueueImpl(); + error InvalidQuery(); /// core dependencies address public immutable balanceTracker; @@ -33,6 +34,8 @@ contract EulerAggregationVaultFactory is Ownable { mapping(address => bool) public isWhitelistedWithdrawalQueueImpl; /// @dev An array of the whitelisted WithdrawalQueue plugin implementations. address[] public withdrawalQueueImpls; + /// @dev Array to store the addresses of all deployed aggregation vaults. + address[] public aggregationVaults; /// @dev Init params struct. struct FactoryParams { @@ -44,6 +47,11 @@ contract EulerAggregationVaultFactory is Ownable { address rebalancer; } + event WhitelistWithdrawalQueueImpl(address withdrawalQueueImpl); + event DeployEulerAggregationVault( + address indexed _owner, address _aggregationVault, address indexed _withdrawalQueueImpl, address indexed _asset + ); + /// @notice Constructor. /// @param _owner Factory owner. /// @param _factoryParams FactoryParams struct. @@ -57,12 +65,17 @@ contract EulerAggregationVaultFactory is Ownable { rebalancer = _factoryParams.rebalancer; } + /// @notice Whitelist a new WithdrawalQueue implementation. + /// @dev Can only be called by factory owner. + /// @param _withdrawalQueuImpl WithdrawalQueue plugin implementation. function whitelistWithdrawalQueueImpl(address _withdrawalQueuImpl) external onlyOwner { if (isWhitelistedWithdrawalQueueImpl[_withdrawalQueuImpl]) revert WithdrawalQueueAlreadyWhitelisted(); isWhitelistedWithdrawalQueueImpl[_withdrawalQueuImpl] = true; withdrawalQueueImpls.push(_withdrawalQueuImpl); + + emit WhitelistWithdrawalQueueImpl(_withdrawalQueuImpl); } /// @notice Deploy a new aggregation layer vault. @@ -110,6 +123,52 @@ contract EulerAggregationVaultFactory is Ownable { withdrawalQueue.init(msg.sender, address(eulerAggregationVault)); eulerAggregationVault.init(aggregationVaultInitParams); + aggregationVaults.push(address(eulerAggregationVault)); + + emit DeployEulerAggregationVault(msg.sender, address(eulerAggregationVault), _withdrawalQueueImpl, _asset); + return address(eulerAggregationVault); } + + /// @notice Fetch the length of the whitelisted WithdrawalQueue implementations list + /// @return The length of the proxy list array + function getWithdrawalQueueImplsListLength() external view returns (uint256) { + return withdrawalQueueImpls.length; + } + + /// @notice Return the WithdrawalQueue implementations list. + /// @return withdrawalQueueImplsList An array of addresses. + function getWithdrawalQueueImplsList() external view returns (address[] memory) { + uint256 length = withdrawalQueueImpls.length; + address[] memory withdrawalQueueImplsList = new address[](length); + + for (uint256 i; i < length; ++i) { + withdrawalQueueImplsList[i] = withdrawalQueueImpls[i]; + } + + return withdrawalQueueImplsList; + } + + /// @notice Fetch the length of the deployed aggregation vaults list. + /// @return The length of the aggregation vaults list array. + function getAggregationVaultsListLength() external view returns (uint256) { + return aggregationVaults.length; + } + + /// @notice Get a slice of the deployed aggregation vaults array. + /// @param _start Start index of the slice. + /// @param _end End index of the slice. + /// @return aggregationVaultsList An array containing the slice of the deployed aggregation vaults list. + function getAggregationVaultsListSlice(uint256 _start, uint256 _end) external view returns (address[] memory) { + uint256 length = aggregationVaults.length; + if (_end == type(uint256).max) _end = length; + if (_end < _start || _end > length) revert InvalidQuery(); + + address[] memory aggregationVaultsList = new address[](_end - _start); + for (uint256 i; i < _end - _start; ++i) { + aggregationVaultsList[i] = aggregationVaults[_start + i]; + } + + return aggregationVaultsList; + } } diff --git a/src/core/lib/HooksLib.sol b/src/core/lib/HooksLib.sol index 7bfedbc6..28230569 100644 --- a/src/core/lib/HooksLib.sol +++ b/src/core/lib/HooksLib.sol @@ -7,11 +7,6 @@ pragma solidity ^0.8.0; /// @notice Library for `Hooks` custom type /// @dev This is copied from https://github.com/euler-xyz/euler-vault-kit/blob/30b0b9e36b0a912fe430c7482e9b3bb12d180a4e/src/EVault/shared/types/Flags.sol library HooksLib { - /// @dev Are *all* of the Hooks in bitMask set? - function isSet(uint32 _hookedFns, uint32 _fn) internal pure returns (bool) { - return (_hookedFns & _fn) == _fn; - } - /// @dev Are *none* of the Hooks in bitMask set? function isNotSet(uint32 _hookedFns, uint32 _fn) internal pure returns (bool) { return (_hookedFns & _fn) == 0; From d0d1c9678beeee2b0ff0af07f70b93438990074e Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:55:18 +0300 Subject: [PATCH 238/316] add slither --- .github/workflows/slither.yml | 31 +++++++++++++++++++++++++++++++ slither.config.json | 21 +++++++++++++++++++++ slither.db.json | 1 + src/core/lib/ErrorsLib.sol | 1 - 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/slither.yml create mode 100644 slither.config.json create mode 100644 slither.db.json diff --git a/.github/workflows/slither.yml b/.github/workflows/slither.yml new file mode 100644 index 00000000..11113dd3 --- /dev/null +++ b/.github/workflows/slither.yml @@ -0,0 +1,31 @@ +name: Slither Analysis + +on: + push: + branches: + - main + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + analyze: + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Run Slither + uses: crytic/slither-action@v0.3.0 + id: slither + with: + node-version: 16 + sarif: results.sarif + fail-on: high + continue-on-error : true \ No newline at end of file diff --git a/slither.config.json b/slither.config.json new file mode 100644 index 00000000..5e4b00c5 --- /dev/null +++ b/slither.config.json @@ -0,0 +1,21 @@ +{ + "detectors_to_run": "abiencoderv2-array,array-by-reference,encode-packed-collision,incorrect-shift,name-reused,shadowing-state,uninitialized-state,uninitialized-storage,unprotected-upgrade,incorrect-return,storage-array,shadowing-abstract,write-after-write,constant-function-state,unused-return,variable-scope,redundant-statements,unimplemented-functions,unused-import,unused-state,dead-code,constable-states,external-function,immutable-states,var-read-using-this", + "exclude_informational": false, + "exclude_low": false, + "exclude_medium": false, + "exclude_high": false, + "disable_color": false, + "solc_remaps": [ + "ds-test/=lib/ethereum-vault-connector/lib/forge-std/lib/ds-test/src/", + "erc4626-tests/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/lib/erc4626-tests/", + "ethereum-vault-connector/=lib/ethereum-vault-connector/src/", + "forge-std/=lib/forge-std/src/", + "evk/=lib/euler-vault-kit/", + "reward-streams=lib/reward-streams/src", + "openzeppelin-contracts/=lib/reward-streams/lib/openzeppelin-contracts/contracts", + "@openzeppelin/=lib/openzeppelin-contracts/", + "@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", + "crytic-properties/=lib/properties/contracts/" + ], + "compile_force_framework": "foundry" +} diff --git a/slither.db.json b/slither.db.json new file mode 100644 index 00000000..f2681a9f --- /dev/null +++ b/slither.db.json @@ -0,0 +1 @@ +[{"elements": [{"type": "function", "name": "adjustAllocationPoints", "source_mapping": {"start": 6633, "length": 185, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [159, 160, 161, 162, 163, 164], "starting_column": 5, "ending_column": 7}, "type_specific_fields": {"parent": {"type": "contract", "name": "EulerAggregationVault", "source_mapping": {"start": 1605, "length": 24181, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647], "starting_column": 1, "ending_column": 2}}, "signature": "adjustAllocationPoints(address,uint256)"}}, {"type": "function", "name": "use", "source_mapping": {"start": 1637, "length": 152, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [35, 36, 37, 38], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "use(address)"}}, {"type": "node", "name": "return(uint256,uint256)(0,returndatasize()())", "source_mapping": {"start": 2143, "length": 27, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [47], "starting_column": 23, "ending_column": 50}, "type_specific_fields": {"parent": {"type": "function", "name": "_delegateToModule", "source_mapping": {"start": 1795, "length": 393, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "_delegateToModule(address)"}}}}], "description": "EulerAggregationVault.adjustAllocationPoints(address,uint256) (src/core/EulerAggregationVault.sol#159-164) calls Dispatch.use(address) (src/core/Dispatch.sol#35-38) which halt the execution return(uint256,uint256)(0,returndatasize()()) (src/core/Dispatch.sol#47)\n", "markdown": "[EulerAggregationVault.adjustAllocationPoints(address,uint256)](src/core/EulerAggregationVault.sol#L159-L164) calls [Dispatch.use(address)](src/core/Dispatch.sol#L35-L38) which halt the execution [return(uint256,uint256)(0,returndatasize()())](src/core/Dispatch.sol#L47)\n", "first_markdown_element": "src/core/EulerAggregationVault.sol#L159-L164", "id": "d915c06d21a87c4958d56177f277f41190f95773c2fa987afbdaade478d14c2e", "check": "incorrect-return", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "setStrategyCap", "source_mapping": {"start": 6882, "length": 171, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [167, 168, 169, 170, 171, 172], "starting_column": 5, "ending_column": 7}, "type_specific_fields": {"parent": {"type": "contract", "name": "EulerAggregationVault", "source_mapping": {"start": 1605, "length": 24181, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647], "starting_column": 1, "ending_column": 2}}, "signature": "setStrategyCap(address,uint256)"}}, {"type": "function", "name": "use", "source_mapping": {"start": 1637, "length": 152, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [35, 36, 37, 38], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "use(address)"}}, {"type": "node", "name": "return(uint256,uint256)(0,returndatasize()())", "source_mapping": {"start": 2143, "length": 27, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [47], "starting_column": 23, "ending_column": 50}, "type_specific_fields": {"parent": {"type": "function", "name": "_delegateToModule", "source_mapping": {"start": 1795, "length": 393, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "_delegateToModule(address)"}}}}], "description": "EulerAggregationVault.setStrategyCap(address,uint256) (src/core/EulerAggregationVault.sol#167-172) calls Dispatch.use(address) (src/core/Dispatch.sol#35-38) which halt the execution return(uint256,uint256)(0,returndatasize()()) (src/core/Dispatch.sol#47)\n", "markdown": "[EulerAggregationVault.setStrategyCap(address,uint256)](src/core/EulerAggregationVault.sol#L167-L172) calls [Dispatch.use(address)](src/core/Dispatch.sol#L35-L38) which halt the execution [return(uint256,uint256)(0,returndatasize()())](src/core/Dispatch.sol#L47)\n", "first_markdown_element": "src/core/EulerAggregationVault.sol#L167-L172", "id": "b2a4c74b041d57efb8bc7f3e7d78a4c6bdf7879ccbdac94025aff8554d01572e", "check": "incorrect-return", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "addStrategy", "source_mapping": {"start": 7114, "length": 176, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [175, 176, 177, 178, 179, 180], "starting_column": 5, "ending_column": 7}, "type_specific_fields": {"parent": {"type": "contract", "name": "EulerAggregationVault", "source_mapping": {"start": 1605, "length": 24181, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647], "starting_column": 1, "ending_column": 2}}, "signature": "addStrategy(address,uint256)"}}, {"type": "function", "name": "use", "source_mapping": {"start": 1637, "length": 152, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [35, 36, 37, 38], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "use(address)"}}, {"type": "node", "name": "return(uint256,uint256)(0,returndatasize()())", "source_mapping": {"start": 2143, "length": 27, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [47], "starting_column": 23, "ending_column": 50}, "type_specific_fields": {"parent": {"type": "function", "name": "_delegateToModule", "source_mapping": {"start": 1795, "length": 393, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "_delegateToModule(address)"}}}}], "description": "EulerAggregationVault.addStrategy(address,uint256) (src/core/EulerAggregationVault.sol#175-180) calls Dispatch.use(address) (src/core/Dispatch.sol#35-38) which halt the execution return(uint256,uint256)(0,returndatasize()()) (src/core/Dispatch.sol#47)\n", "markdown": "[EulerAggregationVault.addStrategy(address,uint256)](src/core/EulerAggregationVault.sol#L175-L180) calls [Dispatch.use(address)](src/core/Dispatch.sol#L35-L38) which halt the execution [return(uint256,uint256)(0,returndatasize()())](src/core/Dispatch.sol#L47)\n", "first_markdown_element": "src/core/EulerAggregationVault.sol#L175-L180", "id": "bf2bcac08c1abcd15d702ab4a83ffe360e24551622cfbf61d6a35bf7b942eb08", "check": "incorrect-return", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "removeStrategy", "source_mapping": {"start": 7354, "length": 154, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [183, 184, 185, 186, 187, 188], "starting_column": 5, "ending_column": 7}, "type_specific_fields": {"parent": {"type": "contract", "name": "EulerAggregationVault", "source_mapping": {"start": 1605, "length": 24181, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647], "starting_column": 1, "ending_column": 2}}, "signature": "removeStrategy(address)"}}, {"type": "function", "name": "use", "source_mapping": {"start": 1637, "length": 152, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [35, 36, 37, 38], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "use(address)"}}, {"type": "node", "name": "return(uint256,uint256)(0,returndatasize()())", "source_mapping": {"start": 2143, "length": 27, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [47], "starting_column": 23, "ending_column": 50}, "type_specific_fields": {"parent": {"type": "function", "name": "_delegateToModule", "source_mapping": {"start": 1795, "length": 393, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "_delegateToModule(address)"}}}}], "description": "EulerAggregationVault.removeStrategy(address) (src/core/EulerAggregationVault.sol#183-188) calls Dispatch.use(address) (src/core/Dispatch.sol#35-38) which halt the execution return(uint256,uint256)(0,returndatasize()()) (src/core/Dispatch.sol#47)\n", "markdown": "[EulerAggregationVault.removeStrategy(address)](src/core/EulerAggregationVault.sol#L183-L188) calls [Dispatch.use(address)](src/core/Dispatch.sol#L35-L38) which halt the execution [return(uint256,uint256)(0,returndatasize()())](src/core/Dispatch.sol#L47)\n", "first_markdown_element": "src/core/EulerAggregationVault.sol#L183-L188", "id": "e3279bf2e5ce035b66fdd50f126381ed98ed968aa9fb3c061ceeb021c3d07fef", "check": "incorrect-return", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "variable", "name": "MAX_PERFORMANCE_FEE", "source_mapping": {"start": 810, "length": 54, "filename_relative": "src/core/module/Fee.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/module/Fee.sol", "filename_short": "src/core/module/Fee.sol", "is_dependency": false, "lines": [19], "starting_column": 5, "ending_column": 59}, "type_specific_fields": {"parent": {"type": "contract", "name": "FeeModule", "source_mapping": {"start": 710, "length": 1355, "filename_relative": "src/core/module/Fee.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/module/Fee.sol", "filename_short": "src/core/module/Fee.sol", "is_dependency": false, "lines": [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49], "starting_column": 1, "ending_column": 2}}}}, {"type": "contract", "name": "EulerAggregationVault", "source_mapping": {"start": 1605, "length": 24181, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647], "starting_column": 1, "ending_column": 2}}], "description": "FeeModule.MAX_PERFORMANCE_FEE (src/core/module/Fee.sol#19) is never used in EulerAggregationVault (src/core/EulerAggregationVault.sol#34-647)\n", "markdown": "[FeeModule.MAX_PERFORMANCE_FEE](src/core/module/Fee.sol#L19) is never used in [EulerAggregationVault](src/core/EulerAggregationVault.sol#L34-L647)\n", "first_markdown_element": "src/core/module/Fee.sol#L19", "id": "31623f8c0d896943238d363dd1794d3cccb3e27d799a7f20b4df426e8a14ba17", "check": "unused-state", "impact": "Informational", "confidence": "High"}] \ No newline at end of file diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index 29e752ff..131aab33 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; library ErrorsLib { error Reentrancy(); - error ArrayLengthMismatch(); error InitialAllocationPointsZero(); error NegativeYield(); error InactiveStrategy(); From d889ad0ec57a02d81b55e19f7656811779a66a60 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:12:56 +0300 Subject: [PATCH 239/316] clean --- remappings.txt | 3 +-- slither.config.json | 3 +-- src/core/common/Shared.sol | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/remappings.txt b/remappings.txt index 7a2b834b..ce0ce0cd 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,5 +1,4 @@ -ds-test/=lib/ethereum-vault-connector/lib/forge-std/lib/ds-test/src/ -erc4626-tests/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/lib/erc4626-tests/ +erc4626-tests/=lib/erc4626-tests/ ethereum-vault-connector/=lib/ethereum-vault-connector/src/ forge-std/=lib/forge-std/src/ evk/=lib/euler-vault-kit/ diff --git a/slither.config.json b/slither.config.json index 5e4b00c5..89172ce6 100644 --- a/slither.config.json +++ b/slither.config.json @@ -6,8 +6,7 @@ "exclude_high": false, "disable_color": false, "solc_remaps": [ - "ds-test/=lib/ethereum-vault-connector/lib/forge-std/lib/ds-test/src/", - "erc4626-tests/=lib/ethereum-vault-connector/lib/openzeppelin-contracts/lib/erc4626-tests/", + "erc4626-tests/=lib/erc4626-tests/", "ethereum-vault-connector/=lib/ethereum-vault-connector/src/", "forge-std/=lib/forge-std/src/", "evk/=lib/euler-vault-kit/", diff --git a/src/core/common/Shared.sol b/src/core/common/Shared.sol index 5fc2a7f9..ce91c75f 100644 --- a/src/core/common/Shared.sol +++ b/src/core/common/Shared.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.0; // interfaces -import {IEVC} from "ethereum-vault-connector/utils/EVCUtil.sol"; import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; // libs import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; From a88bc5a6161d47977d2e7d6e9587ebcec8688226 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:11:14 +0300 Subject: [PATCH 240/316] clean --- src/core/EulerAggregationVault.sol | 2 +- src/core/EulerAggregationVaultFactory.sol | 1 - src/core/lib/ErrorsLib.sol | 5 +-- src/core/module/Fee.sol | 5 +-- test/common/EulerAggregationVaultBase.t.sol | 50 +++++++++++++++++++++ test/e2e/PerformanceFeeE2ETest.t.sol | 1 + test/fuzz/GulpFuzzTest.t.sol | 1 + test/unit/AddStrategyTest.t.sol | 13 ++++-- test/unit/AdjustAllocationPointsTest.t.sol | 9 ++++ test/unit/HarvestTest.t.sol | 10 ++++- test/unit/RebalanceTest.t.sol | 1 + test/unit/RemoveStrategy.t.sol | 11 ++++- test/unit/SetRebalancerTest.t.sol | 2 +- 13 files changed, 94 insertions(+), 17 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 98333225..eafeb63b 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -197,7 +197,7 @@ contract EulerAggregationVault is /// @dev Can only be called by an address with the `AGGREGATION_LAYER_MANAGER` role. /// @param _rebalancer New Rebalancer contract address. function setRebalancer(address _rebalancer) external onlyRole(AGGREGATION_LAYER_MANAGER) { - if (_rebalancer == address(0)) revert Errors.InvalidPlugin(); + if (_rebalancer == address(0)) revert Errors.InvalidRebalancerPlugin(); AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index 643de6b9..18f9f9e3 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -8,7 +8,6 @@ import {Fee} from "./module/Fee.sol"; import {WithdrawalQueue} from "../plugin/WithdrawalQueue.sol"; import {EulerAggregationVault, IEulerAggregationVault} from "./EulerAggregationVault.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - // libs import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index 131aab33..e17e65a8 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.0; library ErrorsLib { error Reentrancy(); error InitialAllocationPointsZero(); - error NegativeYield(); error InactiveStrategy(); error InvalidStrategyAsset(); error StrategyAlreadyExist(); @@ -12,9 +11,7 @@ library ErrorsLib { error PerformanceFeeAlreadySet(); error MaxPerformanceFeeExceeded(); error FeeRecipientNotSet(); - error FeeRecipientAlreadySet(); error CanNotRemoveCashReserve(); - error DuplicateInitialStrategy(); error NotSupported(); error AlreadyEnabled(); error AlreadyDisabled(); @@ -23,7 +20,7 @@ library ErrorsLib { error InvalidHookedFns(); error EmptyError(); error NotWithdrawaQueue(); - error InvalidPlugin(); + error InvalidRebalancerPlugin(); error NotRebalancer(); error InvalidAllocationPoints(); error CanNotRemoveStartegyWithAllocatedAmount(); diff --git a/src/core/module/Fee.sol b/src/core/module/Fee.sol index a24d2af7..f76be6e2 100644 --- a/src/core/module/Fee.sol +++ b/src/core/module/Fee.sol @@ -22,11 +22,8 @@ abstract contract FeeModule { /// @param _newFeeRecipient Recipient address function setFeeRecipient(address _newFeeRecipient) external virtual { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - address feeRecipientCached = $.feeRecipient; - if (_newFeeRecipient == feeRecipientCached) revert Errors.FeeRecipientAlreadySet(); - - emit Events.SetFeeRecipient(feeRecipientCached, _newFeeRecipient); + emit Events.SetFeeRecipient($.feeRecipient, _newFeeRecipient); $.feeRecipient = _newFeeRecipient; } diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index 73c6eee4..b4dc1333 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -141,6 +141,56 @@ contract EulerAggregationVaultBase is EVaultTestBase { assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_REMOVER(), manager)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_LAYER_MANAGER(), manager)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); + + assertEq(eulerAggregationVaultFactory.getWithdrawalQueueImplsListLength(), 1); + address[] memory withdrawalQueueList = eulerAggregationVaultFactory.getWithdrawalQueueImplsList(); + assertEq(withdrawalQueueList.length, 1); + assertEq(address(withdrawalQueueList[0]), address(withdrawalQueueImpl)); + + assertEq(eulerAggregationVaultFactory.getAggregationVaultsListLength(), 1); + address[] memory aggregationVaultsList = eulerAggregationVaultFactory.getAggregationVaultsListSlice(0, 1); + assertEq(aggregationVaultsList.length, 1); + assertEq(address(aggregationVaultsList[0]), address(eulerAggregationVault)); + } + + function testWhitelistWithdrawalQueueImpl() public { + address newWithdrawalQueueImpl = makeAddr("NEW_WITHDRAWAL_QUEUE_IMPL"); + + vm.prank(deployer); + eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(newWithdrawalQueueImpl); + + assertEq(eulerAggregationVaultFactory.isWhitelistedWithdrawalQueueImpl(newWithdrawalQueueImpl), true); + address[] memory withdrawalQueueList = eulerAggregationVaultFactory.getWithdrawalQueueImplsList(); + assertEq(withdrawalQueueList.length, 2); + assertEq(address(withdrawalQueueList[1]), address(newWithdrawalQueueImpl)); + + vm.prank(deployer); + vm.expectRevert(EulerAggregationVaultFactory.WithdrawalQueueAlreadyWhitelisted.selector); + eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(newWithdrawalQueueImpl); + } + + function testDeployEulerAggregationVaultWithRandomWithdrawalQueue() public { + address newWithdrawalQueueImpl = makeAddr("NEW_WITHDRAWAL_QUEUE_IMPL"); + + vm.expectRevert(EulerAggregationVaultFactory.NotWhitelistedWithdrawalQueueImpl.selector); + eulerAggregationVaultFactory.deployEulerAggregationVault( + newWithdrawalQueueImpl, address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS + ); + + address[] memory aggregationVaultsList = + eulerAggregationVaultFactory.getAggregationVaultsListSlice(0, type(uint256).max); + assertEq(aggregationVaultsList.length, 1); + assertEq(address(aggregationVaultsList[0]), address(eulerAggregationVault)); + + vm.expectRevert(EulerAggregationVaultFactory.InvalidQuery.selector); + eulerAggregationVaultFactory.getAggregationVaultsListSlice(1, 0); + } + + function testDeployEulerAggregationVaultWithInvalidInitialCashAllocationPoints() public { + vm.expectRevert(ErrorsLib.InitialAllocationPointsZero.selector); + eulerAggregationVaultFactory.deployEulerAggregationVault( + address(withdrawalQueueImpl), address(assetTST), "assetTST_Agg", "assetTST_Agg", 0 + ); } function _addStrategy(address from, address strategy, uint256 allocationPoints) internal { diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 4b2e9883..3ce7d1a7 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -115,6 +115,7 @@ contract PerformanceFeeE2ETest is EulerAggregationVaultBase { vm.prank(user1); eulerAggregationVault.harvest(); + assertGt(expectedPerformanceFee, 0); assertEq(assetTST.balanceOf(feeRecipient), expectedPerformanceFee); assertEq( eulerAggregationVault.getStrategy(address(eTST)).allocated, diff --git a/test/fuzz/GulpFuzzTest.t.sol b/test/fuzz/GulpFuzzTest.t.sol index 602eff00..b0ba904e 100644 --- a/test/fuzz/GulpFuzzTest.t.sol +++ b/test/fuzz/GulpFuzzTest.t.sol @@ -27,6 +27,7 @@ contract GulpFuzzTest is EulerAggregationVaultBase { assetTST.mint(address(eulerAggregationVault), _interestAmount); eulerAggregationVault.gulp(); + eulerAggregationVault.updateInterestAccrued(); vm.warp(_timePassed); uint256 interestAccrued = eulerAggregationVault.interestAccrued(); diff --git a/test/unit/AddStrategyTest.t.sol b/test/unit/AddStrategyTest.t.sol index 1b05ad80..28eac729 100644 --- a/test/unit/AddStrategyTest.t.sol +++ b/test/unit/AddStrategyTest.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {EulerAggregationVaultBase, EulerAggregationVault} from "../common/EulerAggregationVaultBase.t.sol"; +import {EulerAggregationVaultBase, EulerAggregationVault, ErrorsLib} from "../common/EulerAggregationVaultBase.t.sol"; contract AddStrategyTest is EulerAggregationVaultBase { function setUp() public virtual override { @@ -34,7 +34,7 @@ contract AddStrategyTest is EulerAggregationVaultBase { assertEq(_getWithdrawalQueueLength(), 0); - vm.expectRevert(); + vm.expectRevert(ErrorsLib.InvalidStrategyAsset.selector); _addStrategy(manager, address(eTST2), allocationPoints); } @@ -49,7 +49,14 @@ contract AddStrategyTest is EulerAggregationVaultBase { assertEq(eulerAggregationVault.totalAllocationPoints(), allocationPoints + totalAllocationPointsBefore); assertEq(_getWithdrawalQueueLength(), 1); - vm.expectRevert(); + vm.expectRevert(ErrorsLib.StrategyAlreadyExist.selector); + _addStrategy(manager, address(eTST), allocationPoints); + } + + function testAddStrategy_WithInvalidPoints() public { + uint256 allocationPoints = 0; + + vm.expectRevert(ErrorsLib.InvalidAllocationPoints.selector); _addStrategy(manager, address(eTST), allocationPoints); } } diff --git a/test/unit/AdjustAllocationPointsTest.t.sol b/test/unit/AdjustAllocationPointsTest.t.sol index 049f6110..10b994f4 100644 --- a/test/unit/AdjustAllocationPointsTest.t.sol +++ b/test/unit/AdjustAllocationPointsTest.t.sol @@ -52,4 +52,13 @@ contract AdjustAllocationsPointsTest is EulerAggregationVaultBase { eulerAggregationVault.adjustAllocationPoints(address(eTST2), newAllocationPoints); vm.stopPrank(); } + + function testAdjustAllocationPoints_ZeroPoints() public { + uint256 newAllocationPoints = 0; + + vm.startPrank(manager); + vm.expectRevert(ErrorsLib.InvalidAllocationPoints.selector); + eulerAggregationVault.adjustAllocationPoints(address(eTST), newAllocationPoints); + vm.stopPrank(); + } } diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index 369cfe3f..6dda8584 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -6,7 +6,8 @@ import { EulerAggregationVault, console2, EVault, - IEulerAggregationVault + IEulerAggregationVault, + ErrorsLib } from "../common/EulerAggregationVaultBase.t.sol"; contract HarvestTest is EulerAggregationVaultBase { @@ -257,4 +258,11 @@ contract HarvestTest is EulerAggregationVaultBase { assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets + interestToBeAccrued, 1); assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + interestToBeAccrued); } + + function testCallingHarvestMethodsFromRandomSender() public { + vm.startPrank(user1); + vm.expectRevert(ErrorsLib.NotWithdrawaQueue.selector); + eulerAggregationVault.executeStrategyWithdraw(address(eTST), 1); + vm.stopPrank(); + } } diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index 0fe60eab..e8d37921 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -341,6 +341,7 @@ contract RebalanceTest is EulerAggregationVaultBase { strategiesToRebalance[0] = address(eTST); rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + // TODO: check this // assertEq(eulerAggregationVault.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); // assertEq( // eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated - eTSTMaxWithdraw diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index bcc3e46e..d3be60de 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -5,7 +5,8 @@ import { EulerAggregationVaultBase, EulerAggregationVault, IEVault, - IEulerAggregationVault + IEulerAggregationVault, + ErrorsLib } from "../common/EulerAggregationVaultBase.t.sol"; contract RemoveStrategyTest is EulerAggregationVaultBase { @@ -113,7 +114,13 @@ contract RemoveStrategyTest is EulerAggregationVaultBase { function testRemoveStrategy_AlreadyRemoved() public { vm.prank(manager); - vm.expectRevert(); + vm.expectRevert(ErrorsLib.AlreadyRemoved.selector); eulerAggregationVault.removeStrategy(address(eTST2)); } + + function testRemoveCashReserveStrategy() public { + vm.prank(manager); + vm.expectRevert(ErrorsLib.CanNotRemoveCashReserve.selector); + eulerAggregationVault.removeStrategy(address(0)); + } } diff --git a/test/unit/SetRebalancerTest.t.sol b/test/unit/SetRebalancerTest.t.sol index 5199c661..b6c50106 100644 --- a/test/unit/SetRebalancerTest.t.sol +++ b/test/unit/SetRebalancerTest.t.sol @@ -10,7 +10,7 @@ contract SetRebalancerTest is EulerAggregationVaultBase { function testSetInvalidRebalancer() public { vm.startPrank(manager); - vm.expectRevert(ErrorsLib.InvalidPlugin.selector); + vm.expectRevert(ErrorsLib.InvalidRebalancerPlugin.selector); eulerAggregationVault.setRebalancer(address(0)); } From 1fc4174f34f4fa5f1e5f3c29be4151790a5be1cf Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:51:37 +0300 Subject: [PATCH 241/316] chore: delete slither.db.json --- .gitignore | 5 ++++- slither.db.json | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 slither.db.json diff --git a/.gitignore b/.gitignore index a9f0d3a5..9d827283 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,7 @@ coverage # echidna crytic-export/ -/test/echidna/_corpus/ \ No newline at end of file +/test/echidna/_corpus/ + +# slither +slither.db.json \ No newline at end of file diff --git a/slither.db.json b/slither.db.json deleted file mode 100644 index f2681a9f..00000000 --- a/slither.db.json +++ /dev/null @@ -1 +0,0 @@ -[{"elements": [{"type": "function", "name": "adjustAllocationPoints", "source_mapping": {"start": 6633, "length": 185, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [159, 160, 161, 162, 163, 164], "starting_column": 5, "ending_column": 7}, "type_specific_fields": {"parent": {"type": "contract", "name": "EulerAggregationVault", "source_mapping": {"start": 1605, "length": 24181, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647], "starting_column": 1, "ending_column": 2}}, "signature": "adjustAllocationPoints(address,uint256)"}}, {"type": "function", "name": "use", "source_mapping": {"start": 1637, "length": 152, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [35, 36, 37, 38], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "use(address)"}}, {"type": "node", "name": "return(uint256,uint256)(0,returndatasize()())", "source_mapping": {"start": 2143, "length": 27, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [47], "starting_column": 23, "ending_column": 50}, "type_specific_fields": {"parent": {"type": "function", "name": "_delegateToModule", "source_mapping": {"start": 1795, "length": 393, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "_delegateToModule(address)"}}}}], "description": "EulerAggregationVault.adjustAllocationPoints(address,uint256) (src/core/EulerAggregationVault.sol#159-164) calls Dispatch.use(address) (src/core/Dispatch.sol#35-38) which halt the execution return(uint256,uint256)(0,returndatasize()()) (src/core/Dispatch.sol#47)\n", "markdown": "[EulerAggregationVault.adjustAllocationPoints(address,uint256)](src/core/EulerAggregationVault.sol#L159-L164) calls [Dispatch.use(address)](src/core/Dispatch.sol#L35-L38) which halt the execution [return(uint256,uint256)(0,returndatasize()())](src/core/Dispatch.sol#L47)\n", "first_markdown_element": "src/core/EulerAggregationVault.sol#L159-L164", "id": "d915c06d21a87c4958d56177f277f41190f95773c2fa987afbdaade478d14c2e", "check": "incorrect-return", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "setStrategyCap", "source_mapping": {"start": 6882, "length": 171, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [167, 168, 169, 170, 171, 172], "starting_column": 5, "ending_column": 7}, "type_specific_fields": {"parent": {"type": "contract", "name": "EulerAggregationVault", "source_mapping": {"start": 1605, "length": 24181, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647], "starting_column": 1, "ending_column": 2}}, "signature": "setStrategyCap(address,uint256)"}}, {"type": "function", "name": "use", "source_mapping": {"start": 1637, "length": 152, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [35, 36, 37, 38], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "use(address)"}}, {"type": "node", "name": "return(uint256,uint256)(0,returndatasize()())", "source_mapping": {"start": 2143, "length": 27, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [47], "starting_column": 23, "ending_column": 50}, "type_specific_fields": {"parent": {"type": "function", "name": "_delegateToModule", "source_mapping": {"start": 1795, "length": 393, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "_delegateToModule(address)"}}}}], "description": "EulerAggregationVault.setStrategyCap(address,uint256) (src/core/EulerAggregationVault.sol#167-172) calls Dispatch.use(address) (src/core/Dispatch.sol#35-38) which halt the execution return(uint256,uint256)(0,returndatasize()()) (src/core/Dispatch.sol#47)\n", "markdown": "[EulerAggregationVault.setStrategyCap(address,uint256)](src/core/EulerAggregationVault.sol#L167-L172) calls [Dispatch.use(address)](src/core/Dispatch.sol#L35-L38) which halt the execution [return(uint256,uint256)(0,returndatasize()())](src/core/Dispatch.sol#L47)\n", "first_markdown_element": "src/core/EulerAggregationVault.sol#L167-L172", "id": "b2a4c74b041d57efb8bc7f3e7d78a4c6bdf7879ccbdac94025aff8554d01572e", "check": "incorrect-return", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "addStrategy", "source_mapping": {"start": 7114, "length": 176, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [175, 176, 177, 178, 179, 180], "starting_column": 5, "ending_column": 7}, "type_specific_fields": {"parent": {"type": "contract", "name": "EulerAggregationVault", "source_mapping": {"start": 1605, "length": 24181, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647], "starting_column": 1, "ending_column": 2}}, "signature": "addStrategy(address,uint256)"}}, {"type": "function", "name": "use", "source_mapping": {"start": 1637, "length": 152, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [35, 36, 37, 38], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "use(address)"}}, {"type": "node", "name": "return(uint256,uint256)(0,returndatasize()())", "source_mapping": {"start": 2143, "length": 27, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [47], "starting_column": 23, "ending_column": 50}, "type_specific_fields": {"parent": {"type": "function", "name": "_delegateToModule", "source_mapping": {"start": 1795, "length": 393, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "_delegateToModule(address)"}}}}], "description": "EulerAggregationVault.addStrategy(address,uint256) (src/core/EulerAggregationVault.sol#175-180) calls Dispatch.use(address) (src/core/Dispatch.sol#35-38) which halt the execution return(uint256,uint256)(0,returndatasize()()) (src/core/Dispatch.sol#47)\n", "markdown": "[EulerAggregationVault.addStrategy(address,uint256)](src/core/EulerAggregationVault.sol#L175-L180) calls [Dispatch.use(address)](src/core/Dispatch.sol#L35-L38) which halt the execution [return(uint256,uint256)(0,returndatasize()())](src/core/Dispatch.sol#L47)\n", "first_markdown_element": "src/core/EulerAggregationVault.sol#L175-L180", "id": "bf2bcac08c1abcd15d702ab4a83ffe360e24551622cfbf61d6a35bf7b942eb08", "check": "incorrect-return", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "removeStrategy", "source_mapping": {"start": 7354, "length": 154, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [183, 184, 185, 186, 187, 188], "starting_column": 5, "ending_column": 7}, "type_specific_fields": {"parent": {"type": "contract", "name": "EulerAggregationVault", "source_mapping": {"start": 1605, "length": 24181, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647], "starting_column": 1, "ending_column": 2}}, "signature": "removeStrategy(address)"}}, {"type": "function", "name": "use", "source_mapping": {"start": 1637, "length": 152, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [35, 36, 37, 38], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "use(address)"}}, {"type": "node", "name": "return(uint256,uint256)(0,returndatasize()())", "source_mapping": {"start": 2143, "length": 27, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [47], "starting_column": 23, "ending_column": 50}, "type_specific_fields": {"parent": {"type": "function", "name": "_delegateToModule", "source_mapping": {"start": 1795, "length": 393, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [40, 41, 42, 43, 44, 45, 46, 47, 48, 49], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Dispatch", "source_mapping": {"start": 722, "length": 1468, "filename_relative": "src/core/Dispatch.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/Dispatch.sol", "filename_short": "src/core/Dispatch.sol", "is_dependency": false, "lines": [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50], "starting_column": 1, "ending_column": 2}}, "signature": "_delegateToModule(address)"}}}}], "description": "EulerAggregationVault.removeStrategy(address) (src/core/EulerAggregationVault.sol#183-188) calls Dispatch.use(address) (src/core/Dispatch.sol#35-38) which halt the execution return(uint256,uint256)(0,returndatasize()()) (src/core/Dispatch.sol#47)\n", "markdown": "[EulerAggregationVault.removeStrategy(address)](src/core/EulerAggregationVault.sol#L183-L188) calls [Dispatch.use(address)](src/core/Dispatch.sol#L35-L38) which halt the execution [return(uint256,uint256)(0,returndatasize()())](src/core/Dispatch.sol#L47)\n", "first_markdown_element": "src/core/EulerAggregationVault.sol#L183-L188", "id": "e3279bf2e5ce035b66fdd50f126381ed98ed968aa9fb3c061ceeb021c3d07fef", "check": "incorrect-return", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "variable", "name": "MAX_PERFORMANCE_FEE", "source_mapping": {"start": 810, "length": 54, "filename_relative": "src/core/module/Fee.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/module/Fee.sol", "filename_short": "src/core/module/Fee.sol", "is_dependency": false, "lines": [19], "starting_column": 5, "ending_column": 59}, "type_specific_fields": {"parent": {"type": "contract", "name": "FeeModule", "source_mapping": {"start": 710, "length": 1355, "filename_relative": "src/core/module/Fee.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/module/Fee.sol", "filename_short": "src/core/module/Fee.sol", "is_dependency": false, "lines": [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49], "starting_column": 1, "ending_column": 2}}}}, {"type": "contract", "name": "EulerAggregationVault", "source_mapping": {"start": 1605, "length": 24181, "filename_relative": "src/core/EulerAggregationVault.sol", "filename_absolute": "/Users/haythem/Projects/Euler/Lending/euler-aggregation-layer/src/core/EulerAggregationVault.sol", "filename_short": "src/core/EulerAggregationVault.sol", "is_dependency": false, "lines": [34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647], "starting_column": 1, "ending_column": 2}}], "description": "FeeModule.MAX_PERFORMANCE_FEE (src/core/module/Fee.sol#19) is never used in EulerAggregationVault (src/core/EulerAggregationVault.sol#34-647)\n", "markdown": "[FeeModule.MAX_PERFORMANCE_FEE](src/core/module/Fee.sol#L19) is never used in [EulerAggregationVault](src/core/EulerAggregationVault.sol#L34-L647)\n", "first_markdown_element": "src/core/module/Fee.sol#L19", "id": "31623f8c0d896943238d363dd1794d3cccb3e27d799a7f20b4df426e8a14ba17", "check": "unused-state", "impact": "Informational", "confidence": "High"}] \ No newline at end of file From 04e1feaba990aea819e34e4af4ff352042c21dc0 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:52:02 +0300 Subject: [PATCH 242/316] clean; add invariants tests --- src/core/EulerAggregationVault.sol | 106 +++++++++--------- src/core/EulerAggregationVaultFactory.sol | 10 +- src/core/lib/ErrorsLib.sol | 1 + src/core/module/AllocationPoints.sol | 4 + src/core/module/Rewards.sol | 4 +- src/plugin/Rebalancer.sol | 4 +- src/plugin/WithdrawalQueue.sol | 16 +-- test/common/EulerAggregationVaultBase.t.sol | 12 +- test/e2e/BalanceForwarderE2ETest.t.sol | 4 +- .../EulerAggregationLayerInvariants.t.sol | 18 +++ 10 files changed, 101 insertions(+), 78 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index eafeb63b..8cd9d520 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -21,7 +21,7 @@ import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgrade import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {StorageLib, AggregationVaultStorage} from "./lib/StorageLib.sol"; +import {StorageLib as Storage, AggregationVaultStorage} from "./lib/StorageLib.sol"; import {ErrorsLib as Errors} from "./lib/ErrorsLib.sol"; import {EventsLib as Events} from "./lib/EventsLib.sol"; @@ -47,8 +47,8 @@ contract EulerAggregationVault is bytes32 public constant STRATEGY_ADDER_ADMIN = keccak256("STRATEGY_ADDER_ADMIN"); bytes32 public constant STRATEGY_REMOVER = keccak256("STRATEGY_REMOVER"); bytes32 public constant STRATEGY_REMOVER_ADMIN = keccak256("STRATEGY_REMOVER_ADMIN"); - bytes32 public constant AGGREGATION_LAYER_MANAGER = keccak256("AGGREGATION_LAYER_MANAGER"); - bytes32 public constant AGGREGATION_LAYER_MANAGER_ADMIN = keccak256("AGGREGATION_LAYER_MANAGER_ADMIN"); + bytes32 public constant AGGREGATION_VAULT_MANAGER = keccak256("AGGREGATION_VAULT_MANAGER"); + bytes32 public constant AGGREGATION_VAULT_MANAGER_ADMIN = keccak256("AGGREGATION_VAULT_MANAGER_ADMIN"); /// @dev Interest rate smearing period uint256 public constant INTEREST_SMEAR = 2 weeks; @@ -73,10 +73,11 @@ contract EulerAggregationVault is if (_initParams.initialCashAllocationPoints == 0) revert Errors.InitialAllocationPointsZero(); - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); $.locked = REENTRANCYLOCK__UNLOCKED; $.withdrawalQueue = _initParams.withdrawalQueuePlugin; $.rebalancer = _initParams.rebalancerPlugin; + $.balanceTracker = _initParams.balanceTracker; $.strategies[address(0)] = IEulerAggregationVault.Strategy({ allocated: 0, allocationPoints: _initParams.initialCashAllocationPoints.toUint120(), @@ -84,7 +85,6 @@ contract EulerAggregationVault is cap: 0 }); $.totalAllocationPoints = _initParams.initialCashAllocationPoints; - $.balanceTracker = _initParams.balanceTracker; // Setup DEFAULT_ADMIN _grantRole(DEFAULT_ADMIN_ROLE, _initParams.aggregationVaultOwner); @@ -93,25 +93,25 @@ contract EulerAggregationVault is _setRoleAdmin(ALLOCATIONS_MANAGER, ALLOCATIONS_MANAGER_ADMIN); _setRoleAdmin(STRATEGY_ADDER, STRATEGY_ADDER_ADMIN); _setRoleAdmin(STRATEGY_REMOVER, STRATEGY_REMOVER_ADMIN); - _setRoleAdmin(AGGREGATION_LAYER_MANAGER, AGGREGATION_LAYER_MANAGER_ADMIN); + _setRoleAdmin(AGGREGATION_VAULT_MANAGER, AGGREGATION_VAULT_MANAGER_ADMIN); } /// @dev See {FeeModule-setFeeRecipient}. function setFeeRecipient(address _newFeeRecipient) external override - onlyRole(AGGREGATION_LAYER_MANAGER) + onlyRole(AGGREGATION_VAULT_MANAGER) use(feeModule) {} /// @dev See {FeeModule-setPerformanceFee}. - function setPerformanceFee(uint256 _newFee) external override onlyRole(AGGREGATION_LAYER_MANAGER) use(feeModule) {} + function setPerformanceFee(uint256 _newFee) external override onlyRole(AGGREGATION_VAULT_MANAGER) use(feeModule) {} /// @dev See {RewardsModule-optInStrategyRewards}. function optInStrategyRewards(address _strategy) external override - onlyRole(AGGREGATION_LAYER_MANAGER) + onlyRole(AGGREGATION_VAULT_MANAGER) use(rewardsModule) {} @@ -119,7 +119,7 @@ contract EulerAggregationVault is function optOutStrategyRewards(address _strategy) external override - onlyRole(AGGREGATION_LAYER_MANAGER) + onlyRole(AGGREGATION_VAULT_MANAGER) use(rewardsModule) {} @@ -127,7 +127,7 @@ contract EulerAggregationVault is function enableRewardForStrategy(address _strategy, address _reward) external override - onlyRole(AGGREGATION_LAYER_MANAGER) + onlyRole(AGGREGATION_VAULT_MANAGER) use(rewardsModule) {} @@ -135,7 +135,7 @@ contract EulerAggregationVault is function disableRewardForStrategy(address _strategy, address _reward, bool _forfeitRecentReward) external override - onlyRole(AGGREGATION_LAYER_MANAGER) + onlyRole(AGGREGATION_VAULT_MANAGER) use(rewardsModule) {} @@ -143,7 +143,7 @@ contract EulerAggregationVault is function claimStrategyReward(address _strategy, address _reward, address _recipient, bool _forfeitRecentReward) external override - onlyRole(AGGREGATION_LAYER_MANAGER) + onlyRole(AGGREGATION_VAULT_MANAGER) use(rewardsModule) {} @@ -151,7 +151,7 @@ contract EulerAggregationVault is function setHooksConfig(address _hooksTarget, uint32 _hookedFns) external override - onlyRole(AGGREGATION_LAYER_MANAGER) + onlyRole(AGGREGATION_VAULT_MANAGER) use(hooksModule) {} @@ -194,12 +194,12 @@ contract EulerAggregationVault is function disableBalanceForwarder() external override use(rewardsModule) {} /// @notice Set a new address for Rebalancer plugin. - /// @dev Can only be called by an address with the `AGGREGATION_LAYER_MANAGER` role. + /// @dev Can only be called by an address with the `AGGREGATION_VAULT_MANAGER` role. /// @param _rebalancer New Rebalancer contract address. - function setRebalancer(address _rebalancer) external onlyRole(AGGREGATION_LAYER_MANAGER) { + function setRebalancer(address _rebalancer) external onlyRole(AGGREGATION_VAULT_MANAGER) { if (_rebalancer == address(0)) revert Errors.InvalidRebalancerPlugin(); - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); emit Events.SetRebalancer($.rebalancer, _rebalancer); @@ -212,7 +212,7 @@ contract EulerAggregationVault is /// @param _amountToRebalance Amount to deposit or withdraw. /// @param _isDeposit bool to indicate if it is a deposit or a withdraw. function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external nonReentrant { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); if (_msgSender() != $.rebalancer) revert Errors.NotRebalancer(); @@ -259,7 +259,7 @@ contract EulerAggregationVault is function executeStrategyWithdraw(address _strategy, uint256 _withdrawAmount) external { _isCallerWithdrawalQueue(); - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); // Update allocated assets $.strategies[_strategy].allocated -= uint120(_withdrawAmount); @@ -271,24 +271,24 @@ contract EulerAggregationVault is /// @notice Execute a withdraw from the AggregationVault /// @dev This function should be called and can only be called by the WithdrawalQueue. - /// @param caller Withdraw call initiator. - /// @param receiver Receiver of the withdrawn asset. - /// @param owner Owner of shares to withdraw against. - /// @param assets Amount of asset to withdraw. - /// @param shares Amount of shares to withdraw against. + /// @param _caller Withdraw call initiator. + /// @param _receiver Receiver of the withdrawn asset. + /// @param _owner Owner of shares to withdraw against. + /// @param _assets Amount of asset to withdraw. + /// @param _shares Amount of shares to withdraw against. function executeAggregationVaultWithdraw( - address caller, - address receiver, - address owner, - uint256 assets, - uint256 shares + address _caller, + address _receiver, + address _owner, + uint256 _assets, + uint256 _shares ) external { _isCallerWithdrawalQueue(); - super._withdraw(caller, receiver, owner, assets, shares); + super._withdraw(_caller, _receiver, _owner, _assets, _shares); - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - $.totalAssetsDeposited -= assets; + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + $.totalAssetsDeposited -= _assets; _gulp(); } @@ -297,7 +297,7 @@ contract EulerAggregationVault is /// @param _strategy strategy's address /// @return Strategy struct function getStrategy(address _strategy) external view returns (IEulerAggregationVault.Strategy memory) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); return $.strategies[_strategy]; } @@ -311,7 +311,7 @@ contract EulerAggregationVault is /// @notice Get saving rate data. /// @return avsr AggregationVaultSavingRate struct. function getAggregationVaultSavingRate() external view returns (AggregationVaultSavingRate memory) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); AggregationVaultSavingRate memory avsr = AggregationVaultSavingRate({ lastInterestUpdate: $.lastInterestUpdate, interestSmearEnd: $.interestSmearEnd, @@ -325,7 +325,7 @@ contract EulerAggregationVault is /// @notice Get the total allocated amount. /// @return uint256 Total allocated. function totalAllocated() external view returns (uint256) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); return $.totalAllocated; } @@ -333,15 +333,15 @@ contract EulerAggregationVault is /// @notice Get the total allocation points. /// @return uint256 Total allocation points. function totalAllocationPoints() external view returns (uint256) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); return $.totalAllocationPoints; } - /// @notice Get the total assets deposited into the aggregation layer. + /// @notice Get the total assets deposited into the aggregation vault. /// @return uint256 Total assets deposited. function totalAssetsDeposited() external view returns (uint256) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); return $.totalAssetsDeposited; } @@ -349,7 +349,7 @@ contract EulerAggregationVault is /// @notice Get the WithdrawalQueue plugin address. /// @return address Withdrawal queue address. function withdrawalQueue() external view returns (address) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); return $.withdrawalQueue; } @@ -357,7 +357,7 @@ contract EulerAggregationVault is /// @notice Get the Rebalancer plugin address. /// @return address Rebalancer address. function rebalancer() external view returns (address) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); return $.rebalancer; } @@ -366,7 +366,7 @@ contract EulerAggregationVault is /// @return adddress Fee recipient. /// @return uint256 Fee percentage. function performanceFeeConfig() external view returns (address, uint256) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); return ($.feeRecipient, $.performanceFee); } @@ -426,7 +426,7 @@ contract EulerAggregationVault is /// @notice Return the total amount of assets deposited, plus the accrued interest. /// @return uint256 total amount function totalAssets() public view override returns (uint256) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); return $.totalAssetsDeposited + _interestAccruedFromCache(); } @@ -435,7 +435,7 @@ contract EulerAggregationVault is /// @dev the total assets allocatable is the amount of assets deposited into the aggregator + assets already deposited into strategies /// @return uint256 total assets function totalAssetsAllocatable() public view returns (uint256) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); return IERC20(asset()).balanceOf(address(this)) + $.totalAllocated; } @@ -445,7 +445,7 @@ contract EulerAggregationVault is function _deposit(address _caller, address _receiver, uint256 _assets, uint256 _shares) internal override { super._deposit(_caller, _receiver, _assets, _shares); - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); $.totalAssetsDeposited += _assets; } @@ -455,7 +455,7 @@ contract EulerAggregationVault is internal override { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); IWithdrawalQueue($.withdrawalQueue).callWithdrawalQueue( @@ -467,7 +467,7 @@ contract EulerAggregationVault is function _updateInterestAccrued() internal { uint256 accruedInterest = _interestAccruedFromCache(); - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); // it's safe to down-cast because the accrued interest is a fraction of interest left $.interestLeft -= uint168(accruedInterest); $.lastInterestUpdate = uint40(block.timestamp); @@ -480,7 +480,7 @@ contract EulerAggregationVault is function _gulp() internal { _updateInterestAccrued(); - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); // Do not gulp if total supply is too low if (totalSupply() < MIN_SHARES_FOR_GULP) return; @@ -500,7 +500,7 @@ contract EulerAggregationVault is /// @dev Loop through stratgies, aggregate positive yield and loss and account for net amount. /// @dev Loss socialization will be taken out from interest left first, if not enough, sozialize on deposits. function _harvest() internal { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); (address[] memory withdrawalQueueArray, uint256 length) = IWithdrawalQueue($.withdrawalQueue).getWithdrawalQueueArray(); @@ -538,10 +538,10 @@ contract EulerAggregationVault is /// @dev Execute harvest on a single strategy. /// @param _strategy Strategy address. - /// @return yiled Amount of yield if any, else 0. + /// @return yield Amount of yield if any, else 0. /// @return loss Amount of loss if any, else 0. function _executeHarvest(address _strategy) internal returns (uint256, uint256) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); uint120 strategyAllocatedAmount = $.strategies[_strategy].allocated; @@ -577,7 +577,7 @@ contract EulerAggregationVault is /// @param _yield Amount of yield harvested. /// @return feeAssets Amount of performance fee taken. function _accruePerformanceFee(address _strategy, uint256 _yield) internal returns (uint120) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); address cachedFeeRecipient = $.feeRecipient; uint256 cachedPerformanceFee = $.performanceFee; @@ -619,7 +619,7 @@ contract EulerAggregationVault is /// @dev Get accrued interest without updating it. /// @return uint256 Accrued interest. function _interestAccruedFromCache() internal view returns (uint256) { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); // If distribution ended, full amount is accrued if (block.timestamp >= $.interestSmearEnd) { @@ -640,7 +640,7 @@ contract EulerAggregationVault is /// @dev Check if caller is WithdrawalQueue address, if not revert. function _isCallerWithdrawalQueue() internal view { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); if (_msgSender() != $.withdrawalQueue) revert Errors.NotWithdrawaQueue(); } diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index 18f9f9e3..36401fb4 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -77,15 +77,15 @@ contract EulerAggregationVaultFactory is Ownable { emit WhitelistWithdrawalQueueImpl(_withdrawalQueuImpl); } - /// @notice Deploy a new aggregation layer vault. - /// @dev This will clone a new WithdrawalQueue plugin instance for the aggregation layer vault. + /// @notice Deploy a new aggregation vault. + /// @dev This will clone a new WithdrawalQueue plugin instance for the aggregation vault. /// @dev This will use the defaut Rebalancer plugin configured in this factory. - /// @dev Both plugins are possible to change by the aggregation layer vault manager. - /// @param _asset Aggreation layer vault' asset address. + /// @dev Both plugins are possible to change by the aggregation vault manager. + /// @param _asset Aggreation vault' asset address. /// @param _name Vaut name. /// @param _symbol Vault symbol. /// @param _initialCashAllocationPoints The amount of points to initally allocate for cash reserve. - /// @return eulerAggregationVault The address of the new deployed aggregation layer vault. + /// @return eulerAggregationVault The address of the new deployed aggregation vault. function deployEulerAggregationVault( address _withdrawalQueueImpl, address _asset, diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index e17e65a8..753a9df6 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -24,4 +24,5 @@ library ErrorsLib { error NotRebalancer(); error InvalidAllocationPoints(); error CanNotRemoveStartegyWithAllocatedAmount(); + error NoCapOnCashReserveStrategy(); } diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index 7ac8f4cc..7c9bec6b 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -50,6 +50,10 @@ abstract contract AllocationPointsModule is Shared { revert Errors.InactiveStrategy(); } + if (_strategy == address(0)) { + revert Errors.NoCapOnCashReserveStrategy(); + } + $.strategies[_strategy].cap = _cap.toUint120(); emit Events.SetStrategyCap(_strategy, _cap); diff --git a/src/core/module/Rewards.sol b/src/core/module/Rewards.sol index ca0ee81f..780fbdc0 100644 --- a/src/core/module/Rewards.sol +++ b/src/core/module/Rewards.sol @@ -39,7 +39,7 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { emit Events.OptOutStrategyRewards(_strategy); } - /// @notice Enable aggregation layer vault rewards for specific strategy's reward token. + /// @notice Enable aggregation vault rewards for specific strategy's reward token. /// @param _strategy Strategy address. /// @param _reward Reward token address. function enableRewardForStrategy(address _strategy, address _reward) external virtual nonReentrant { @@ -52,7 +52,7 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { emit Events.EnableRewardForStrategy(_strategy, _reward); } - /// @notice Disable aggregation layer vault rewards for specific strategy's reward token. + /// @notice Disable aggregation vault rewards for specific strategy's reward token. /// @param _strategy Strategy address. /// @param _reward Reward token address. /// @param _forfeitRecentReward Whether to forfeit the recent rewards or not. diff --git a/src/plugin/Rebalancer.sol b/src/plugin/Rebalancer.sol index 506639d8..c9e37879 100644 --- a/src/plugin/Rebalancer.sol +++ b/src/plugin/Rebalancer.sol @@ -19,7 +19,7 @@ contract Rebalancer { ); /// @notice Rebalance strategies allocation for a specific curated vault. - /// @param _aggregationVault Aggregation layer vault address. + /// @param _aggregationVault Aggregation vault address. /// @param _strategies Strategies addresses. function executeRebalance(address _aggregationVault, address[] calldata _strategies) external { IEulerAggregationVault(_aggregationVault).gulp(); @@ -33,7 +33,7 @@ contract Rebalancer { /// If current allocation is less than target allocation, the aggregator will: /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash /// - If all the available cash is greater than the max deposit, deposit the max deposit - /// @param _aggregationVault Aggregation layer vault address. + /// @param _aggregationVault Aggregation vault address. /// @param _strategy Strategy address. function _rebalance(address _aggregationVault, address _strategy) private { if (_strategy == address(0)) { diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index efda5995..07fbeba2 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -37,8 +37,8 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue event ReorderWithdrawalQueue(uint8 index1, uint8 index2); /// @notice Initialize WithdrawalQueue. - /// @param _owner Aggregation layer vault owner. - /// @param _eulerAggregationVault Address of aggregation layer vault. + /// @param _owner Aggregation vault owner. + /// @param _eulerAggregationVault Address of aggregation vault. function init(address _owner, address _eulerAggregationVault) external initializer { WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); $.eulerAggregationVault = _eulerAggregationVault; @@ -49,7 +49,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue } /// @notice Add a strategy to withdrawal queue array. - /// @dev Can only be called by the aggregation layer vault's address. + /// @dev Can only be called by the aggregation vault's address. /// @param _strategy Strategy address to add function addStrategyToWithdrawalQueue(address _strategy) external { _isCallerAggregationVault(); @@ -60,7 +60,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue } /// @notice Remove a strategy from withdrawal queue array. - /// @dev Can only be called by the aggregation layer vault's address. + /// @dev Can only be called by the aggregation vault's address. /// @param _strategy Strategy address to add. function removeStrategyFromWithdrawalQueue(address _strategy) external { _isCallerAggregationVault(); @@ -102,14 +102,14 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue emit ReorderWithdrawalQueue(_index1, _index2); } - /// @notice Execute the withdraw initiated in the aggregation layer vault. - /// @dev Can only be called by the aggregation layer vault's address. + /// @notice Execute the withdraw initiated in the aggregation vault. + /// @dev Can only be called by the aggregation vault's address. /// @param _caller Initiator's address of withdraw. /// @param _receiver Withdraw receiver address. /// @param _owner Shares's owner to burn. /// @param _assets Amount of asset to withdraw. /// @param _shares Amount of shares to burn. - /// @param _availableAssets Amount of available asset in aggregation layer vault's cash reserve. + /// @param _availableAssets Amount of available asset in aggregation vault's cash reserve. function callWithdrawalQueue( address _caller, address _receiver, @@ -187,7 +187,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue return $.withdrawalQueue.length; } - /// @dev Check if the msg.sender is the aggregation layer vault. + /// @dev Check if the msg.sender is the aggregation vault. function _isCallerAggregationVault() private view { WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index b4dc1333..41a99ac4 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -81,14 +81,14 @@ contract EulerAggregationVaultBase is EVaultTestBase { eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_ADDER_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_REMOVER_ADMIN(), deployer); - eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_LAYER_MANAGER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_ADDER(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_REMOVER(), manager); - eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_LAYER_MANAGER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); vm.stopPrank(); @@ -122,8 +122,8 @@ contract EulerAggregationVaultBase is EVaultTestBase { eulerAggregationVault.STRATEGY_REMOVER_ADMIN() ); assertEq( - eulerAggregationVault.getRoleAdmin(eulerAggregationVault.AGGREGATION_LAYER_MANAGER()), - eulerAggregationVault.AGGREGATION_LAYER_MANAGER_ADMIN() + eulerAggregationVault.getRoleAdmin(eulerAggregationVault.AGGREGATION_VAULT_MANAGER()), + eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN() ); assertEq( withdrawalQueue.getRoleAdmin(withdrawalQueue.WITHDRAW_QUEUE_MANAGER()), @@ -133,13 +133,13 @@ contract EulerAggregationVaultBase is EVaultTestBase { assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN(), deployer)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_ADDER_ADMIN(), deployer)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_REMOVER_ADMIN(), deployer)); - assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_LAYER_MANAGER_ADMIN(), deployer)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.ALLOCATIONS_MANAGER(), manager)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_ADDER(), manager)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_REMOVER(), manager)); - assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_LAYER_MANAGER(), manager)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); assertEq(eulerAggregationVaultFactory.getWithdrawalQueueImplsListLength(), 1); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 3432d4d6..e4455ca5 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -51,14 +51,14 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { // eulerAggregationVault.grantRole(eulerAggregationVault.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_ADDER_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_REMOVER_ADMIN(), deployer); - eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_LAYER_MANAGER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); // grant roles to manager eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER(), manager); // eulerAggregationVault.grantRole(eulerAggregationVault.WITHDRAW_QUEUE_MANAGER(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_ADDER(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_REMOVER(), manager); - eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_LAYER_MANAGER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index ac91e6a5..5d6d182a 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -46,6 +46,8 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { strategyUtil.includeStrategy(address(eTSTforth)); strategyUtil.includeStrategy(address(eTSTfifth)); strategyUtil.includeStrategy(address(eTSTsixth)); + // cash reserve strategy + strategyUtil.includeStrategy(address(0)); eulerAggregationVaultHandler = new EulerAggregationVaultHandler(eulerAggregationVault, actorUtil, strategyUtil, withdrawalQueue); @@ -96,6 +98,18 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { assertEq(eulerAggregationVault.totalAllocationPoints(), expectedTotalAllocationpoints); } + // Every strategy in the withdrawal queue should have an allocation points > 0. + function invariant_withdrawalQueueStrategiesAllocationPoints() public view { + address withdrawalQueueAddr = eulerAggregationVault.withdrawalQueue(); + + (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = + IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); + + for (uint256 i; i < withdrawalQueueLength; i++) { + assertGt(eulerAggregationVault.getStrategy(withdrawalQueueArray[i]).allocationPoints, 0); + } + } + // If `total allocation points - cash reserve allocation points == 0`(no strategy added), the withdrawal queue length should be zero. // Else, the length should be greater than zero. function invariant_withdrawalQueue() public view { @@ -147,6 +161,10 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { assertGe(aggregationVaultSavingRate.interestLeft, accruedInterest); } + function invariant_cashReserveStrategyCap() public view { + assertEq(eulerAggregationVault.getStrategy(address(0)).cap, 0); + } + function _deployOtherStrategies() private { eTSTsecond = IEVault( factory.createProxy(address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount)) From da4f193ac997eacbea7d06ffa671e6c95f0b5d90 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:25:07 +0300 Subject: [PATCH 243/316] feat: do not harvest or rebalance in-active strategies --- src/core/EulerAggregationVault.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 8cd9d520..5e916300 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -218,6 +218,8 @@ contract EulerAggregationVault is IEulerAggregationVault.Strategy memory strategyData = $.strategies[_strategy]; + if(!strategyData.active) return; + if (_isDeposit) { // Do required approval (safely) and deposit IERC20(asset()).safeIncreaseAllowance(_strategy, _amountToRebalance); @@ -545,7 +547,7 @@ contract EulerAggregationVault is uint120 strategyAllocatedAmount = $.strategies[_strategy].allocated; - if (strategyAllocatedAmount == 0) return (0, 0); + if (strategyAllocatedAmount == 0 || !$.strategies[_strategy].active) return (0, 0); uint256 underlyingBalance = IERC4626(_strategy).maxWithdraw(address(this)); uint256 yield; From 9c43041e785ca0ffe5adf757a57ee8442e111fe4 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:35:20 +0300 Subject: [PATCH 244/316] chore: lint --- src/core/EulerAggregationVault.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 5e916300..da8e21fb 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -218,7 +218,7 @@ contract EulerAggregationVault is IEulerAggregationVault.Strategy memory strategyData = $.strategies[_strategy]; - if(!strategyData.active) return; + if (!strategyData.active) return; if (_isDeposit) { // Do required approval (safely) and deposit From 735fdd0a018d3b63071557d92e4ed27b1389231a Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 10 Jul 2024 12:43:48 +0300 Subject: [PATCH 245/316] circuit-breaker --- src/core/lib/ErrorsLib.sol | 1 - src/core/module/AllocationPoints.sol | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index 753a9df6..e6a3211f 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -23,6 +23,5 @@ library ErrorsLib { error InvalidRebalancerPlugin(); error NotRebalancer(); error InvalidAllocationPoints(); - error CanNotRemoveStartegyWithAllocatedAmount(); error NoCapOnCashReserveStrategy(); } diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index 7c9bec6b..665b1858 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -105,11 +105,12 @@ abstract contract AllocationPointsModule is Shared { if (!strategyStorage.active) { revert Errors.AlreadyRemoved(); } - if (strategyStorage.allocated > 0) revert Errors.CanNotRemoveStartegyWithAllocatedAmount(); _callHooksTarget(REMOVE_STRATEGY, msg.sender); $.totalAllocationPoints -= strategyStorage.allocationPoints; + // we do not reset strategyStorage.allocated, as this will allow removing broken strategy + // and adding them later once fixed(circuit-breaker for harvesting and rebalancing). strategyStorage.active = false; strategyStorage.allocationPoints = 0; strategyStorage.cap = 0; From 2faacf8919df00ae054b7e17f61ee9d0e0d61f2b Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 10 Jul 2024 12:43:54 +0300 Subject: [PATCH 246/316] chore: lint --- src/core/module/AllocationPoints.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index 665b1858..41a5ef9b 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -109,7 +109,7 @@ abstract contract AllocationPointsModule is Shared { _callHooksTarget(REMOVE_STRATEGY, msg.sender); $.totalAllocationPoints -= strategyStorage.allocationPoints; - // we do not reset strategyStorage.allocated, as this will allow removing broken strategy + // we do not reset strategyStorage.allocated, as this will allow removing broken strategy // and adding them later once fixed(circuit-breaker for harvesting and rebalancing). strategyStorage.active = false; strategyStorage.allocationPoints = 0; From 3aeadbf9a156d0719361d60c12669c3820dc729b Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 10 Jul 2024 18:05:46 +0300 Subject: [PATCH 247/316] merge strategy added and remover into strategy operator --- src/core/EulerAggregationVault.sol | 14 ++++++------- src/core/lib/ErrorsLib.sol | 1 + src/core/module/AllocationPoints.sol | 5 +++-- test/common/EulerAggregationVaultBase.t.sol | 20 ++++++------------- test/e2e/BalanceForwarderE2ETest.t.sol | 10 ++++------ .../handler/EulerAggregationVaultHandler.sol | 1 - 6 files changed, 20 insertions(+), 31 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index da8e21fb..e15d2484 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -43,10 +43,8 @@ contract EulerAggregationVault is // Roles bytes32 public constant ALLOCATIONS_MANAGER = keccak256("ALLOCATIONS_MANAGER"); bytes32 public constant ALLOCATIONS_MANAGER_ADMIN = keccak256("ALLOCATIONS_MANAGER_ADMIN"); - bytes32 public constant STRATEGY_ADDER = keccak256("STRATEGY_ADDER"); - bytes32 public constant STRATEGY_ADDER_ADMIN = keccak256("STRATEGY_ADDER_ADMIN"); - bytes32 public constant STRATEGY_REMOVER = keccak256("STRATEGY_REMOVER"); - bytes32 public constant STRATEGY_REMOVER_ADMIN = keccak256("STRATEGY_REMOVER_ADMIN"); + bytes32 public constant STRATEGY_OPERATOR = keccak256("STRATEGY_OPERATOR"); + bytes32 public constant STRATEGY_OPERATOR_ADMIN = keccak256("STRATEGY_OPERATOR_ADMIN"); bytes32 public constant AGGREGATION_VAULT_MANAGER = keccak256("AGGREGATION_VAULT_MANAGER"); bytes32 public constant AGGREGATION_VAULT_MANAGER_ADMIN = keccak256("AGGREGATION_VAULT_MANAGER_ADMIN"); @@ -91,8 +89,8 @@ contract EulerAggregationVault is // Setup role admins _setRoleAdmin(ALLOCATIONS_MANAGER, ALLOCATIONS_MANAGER_ADMIN); - _setRoleAdmin(STRATEGY_ADDER, STRATEGY_ADDER_ADMIN); - _setRoleAdmin(STRATEGY_REMOVER, STRATEGY_REMOVER_ADMIN); + _setRoleAdmin(STRATEGY_OPERATOR, STRATEGY_OPERATOR_ADMIN); + _setRoleAdmin(STRATEGY_OPERATOR, STRATEGY_OPERATOR_ADMIN); _setRoleAdmin(AGGREGATION_VAULT_MANAGER, AGGREGATION_VAULT_MANAGER_ADMIN); } @@ -176,7 +174,7 @@ contract EulerAggregationVault is external override use(allocationPointsModule) - onlyRole(STRATEGY_ADDER) + onlyRole(STRATEGY_OPERATOR) {} /// @dev See {AllocationPointsModule-removeStrategy}. @@ -184,7 +182,7 @@ contract EulerAggregationVault is external override use(allocationPointsModule) - onlyRole(STRATEGY_REMOVER) + onlyRole(STRATEGY_OPERATOR) {} /// @dev See {RewardsModule-enableBalanceForwarder}. diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index e6a3211f..753a9df6 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -23,5 +23,6 @@ library ErrorsLib { error InvalidRebalancerPlugin(); error NotRebalancer(); error InvalidAllocationPoints(); + error CanNotRemoveStartegyWithAllocatedAmount(); error NoCapOnCashReserveStrategy(); } diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index 41a5ef9b..1f8160b5 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -60,7 +60,7 @@ abstract contract AllocationPointsModule is Shared { } /// @notice Add new strategy with it's allocation points. - /// @dev Can only be called by an address that have STRATEGY_ADDER. + /// @dev Can only be called by an address that have STRATEGY_OPERATOR. /// @param _strategy Address of the strategy /// @param _allocationPoints Strategy's allocation points function addStrategy(address _strategy, uint256 _allocationPoints) external virtual nonReentrant { @@ -93,7 +93,7 @@ abstract contract AllocationPointsModule is Shared { /// @notice Remove strategy and set its allocation points to zero. /// @dev This function does not pull funds, `harvest()` needs to be called to withdraw - /// @dev Can only be called by an address that have the STRATEGY_REMOVER + /// @dev Can only be called by an address that have the STRATEGY_OPERATOR /// @param _strategy Address of the strategy function removeStrategy(address _strategy) external virtual nonReentrant { if (_strategy == address(0)) revert Errors.CanNotRemoveCashReserve(); @@ -105,6 +105,7 @@ abstract contract AllocationPointsModule is Shared { if (!strategyStorage.active) { revert Errors.AlreadyRemoved(); } + if (strategyStorage.allocated > 0) revert Errors.CanNotRemoveStartegyWithAllocatedAmount(); _callHooksTarget(REMOVE_STRATEGY, msg.sender); diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index 41a99ac4..82c5161d 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -79,15 +79,13 @@ contract EulerAggregationVaultBase is EVaultTestBase { // grant admin roles to deployer eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN(), deployer); - eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_ADDER_ADMIN(), deployer); - eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_REMOVER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER(), manager); - eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_ADDER(), manager); - eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_REMOVER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); @@ -114,12 +112,8 @@ contract EulerAggregationVaultBase is EVaultTestBase { eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN() ); assertEq( - eulerAggregationVault.getRoleAdmin(eulerAggregationVault.STRATEGY_ADDER()), - eulerAggregationVault.STRATEGY_ADDER_ADMIN() - ); - assertEq( - eulerAggregationVault.getRoleAdmin(eulerAggregationVault.STRATEGY_REMOVER()), - eulerAggregationVault.STRATEGY_REMOVER_ADMIN() + eulerAggregationVault.getRoleAdmin(eulerAggregationVault.STRATEGY_OPERATOR()), + eulerAggregationVault.STRATEGY_OPERATOR_ADMIN() ); assertEq( eulerAggregationVault.getRoleAdmin(eulerAggregationVault.AGGREGATION_VAULT_MANAGER()), @@ -131,14 +125,12 @@ contract EulerAggregationVaultBase is EVaultTestBase { ); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN(), deployer)); - assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_ADDER_ADMIN(), deployer)); - assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_REMOVER_ADMIN(), deployer)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_OPERATOR_ADMIN(), deployer)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.ALLOCATIONS_MANAGER(), manager)); - assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_ADDER(), manager)); - assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_REMOVER(), manager)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_OPERATOR(), manager)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index e4455ca5..08f75056 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -48,17 +48,15 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { // grant admin roles to deployer eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN(), deployer); - // eulerAggregationVault.grantRole(eulerAggregationVault.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); - eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_ADDER_ADMIN(), deployer); - eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_REMOVER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); + withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER(), manager); - // eulerAggregationVault.grantRole(eulerAggregationVault.WITHDRAW_QUEUE_MANAGER(), manager); - eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_ADDER(), manager); - eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_REMOVER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager); + withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index 134252ee..841eca59 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -56,7 +56,6 @@ contract EulerAggregationVaultHandler is Test { function setFeeRecipient(string calldata _feeRecipientSeed) external { address feeRecipientAddr = makeAddr(_feeRecipientSeed); - // vm.assume(_newFeeRecipient != address(0)); (currentActor, success, returnData) = actorUtil.initiateExactActorCall( 0, From 9522d10ff0310bbc88866d5d0aedbdffd6d03fb6 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 10 Jul 2024 21:11:03 +0300 Subject: [PATCH 248/316] add startegy status --- src/core/EulerAggregationVault.sol | 46 ++++++++++++------ src/core/interface/IEulerAggregationVault.sol | 11 ++++- src/core/lib/ErrorsLib.sol | 1 + src/core/module/AllocationPoints.sol | 48 ++++++++++++------- src/core/module/Rewards.sol | 13 +++-- test/common/EulerAggregationVaultBase.t.sol | 12 ++--- test/e2e/BalanceForwarderE2ETest.t.sol | 4 +- test/unit/AdjustAllocationPointsTest.t.sol | 2 +- test/unit/RemoveStrategy.t.sol | 4 +- 9 files changed, 92 insertions(+), 49 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index e15d2484..618c367f 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -41,8 +41,8 @@ contract EulerAggregationVault is using SafeCast for uint256; // Roles - bytes32 public constant ALLOCATIONS_MANAGER = keccak256("ALLOCATIONS_MANAGER"); - bytes32 public constant ALLOCATIONS_MANAGER_ADMIN = keccak256("ALLOCATIONS_MANAGER_ADMIN"); + bytes32 public constant GUARDIAN = keccak256("GUARDIAN"); + bytes32 public constant GUARDIAN_ADMIN = keccak256("GUARDIAN_ADMIN"); bytes32 public constant STRATEGY_OPERATOR = keccak256("STRATEGY_OPERATOR"); bytes32 public constant STRATEGY_OPERATOR_ADMIN = keccak256("STRATEGY_OPERATOR_ADMIN"); bytes32 public constant AGGREGATION_VAULT_MANAGER = keccak256("AGGREGATION_VAULT_MANAGER"); @@ -79,7 +79,7 @@ contract EulerAggregationVault is $.strategies[address(0)] = IEulerAggregationVault.Strategy({ allocated: 0, allocationPoints: _initParams.initialCashAllocationPoints.toUint120(), - active: true, + status: IEulerAggregationVault.StrategyStatus.Active, cap: 0 }); $.totalAllocationPoints = _initParams.initialCashAllocationPoints; @@ -88,7 +88,6 @@ contract EulerAggregationVault is _grantRole(DEFAULT_ADMIN_ROLE, _initParams.aggregationVaultOwner); // Setup role admins - _setRoleAdmin(ALLOCATIONS_MANAGER, ALLOCATIONS_MANAGER_ADMIN); _setRoleAdmin(STRATEGY_OPERATOR, STRATEGY_OPERATOR_ADMIN); _setRoleAdmin(STRATEGY_OPERATOR, STRATEGY_OPERATOR_ADMIN); _setRoleAdmin(AGGREGATION_VAULT_MANAGER, AGGREGATION_VAULT_MANAGER_ADMIN); @@ -158,7 +157,23 @@ contract EulerAggregationVault is external override use(allocationPointsModule) - onlyRole(ALLOCATIONS_MANAGER) + onlyRole(STRATEGY_OPERATOR) + {} + + /// @dev See {AllocationPointsModule-addStrategy}. + function addStrategy(address _strategy, uint256 _allocationPoints) + external + override + use(allocationPointsModule) + onlyRole(STRATEGY_OPERATOR) + {} + + /// @dev See {AllocationPointsModule-removeStrategy}. + function removeStrategy(address _strategy) + external + override + use(allocationPointsModule) + onlyRole(STRATEGY_OPERATOR) {} /// @dev See {AllocationPointsModule-setStrategyCap}. @@ -166,23 +181,23 @@ contract EulerAggregationVault is external override use(allocationPointsModule) - onlyRole(ALLOCATIONS_MANAGER) + onlyRole(GUARDIAN) {} - /// @dev See {AllocationPointsModule-addStrategy}. - function addStrategy(address _strategy, uint256 _allocationPoints) + /// @dev See {AllocationPointsModule-activateStrategyEmergency}. + function activateStrategyEmergency(address _strategy) external override use(allocationPointsModule) - onlyRole(STRATEGY_OPERATOR) + onlyRole(GUARDIAN) {} - /// @dev See {AllocationPointsModule-removeStrategy}. - function removeStrategy(address _strategy) + /// @dev See {AllocationPointsModule-deactivateStrategyEmergency}. + function deactivateStrategyEmergency(address _strategy) external override use(allocationPointsModule) - onlyRole(STRATEGY_OPERATOR) + onlyRole(GUARDIAN) {} /// @dev See {RewardsModule-enableBalanceForwarder}. @@ -216,7 +231,7 @@ contract EulerAggregationVault is IEulerAggregationVault.Strategy memory strategyData = $.strategies[_strategy]; - if (!strategyData.active) return; + if (strategyData.status != IEulerAggregationVault.StrategyStatus.Active) return; if (_isDeposit) { // Do required approval (safely) and deposit @@ -545,7 +560,10 @@ contract EulerAggregationVault is uint120 strategyAllocatedAmount = $.strategies[_strategy].allocated; - if (strategyAllocatedAmount == 0 || !$.strategies[_strategy].active) return (0, 0); + if ( + strategyAllocatedAmount == 0 + || $.strategies[_strategy].status != IEulerAggregationVault.StrategyStatus.Active + ) return (0, 0); uint256 underlyingBalance = IERC4626(_strategy).maxWithdraw(address(this)); uint256 yield; diff --git a/src/core/interface/IEulerAggregationVault.sol b/src/core/interface/IEulerAggregationVault.sol index 31074c85..dfa36c75 100644 --- a/src/core/interface/IEulerAggregationVault.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -17,13 +17,14 @@ interface IEulerAggregationVault { /// @dev A struct that hold a strategy allocation's config /// allocated: amount of asset deposited into strategy /// allocationPoints: number of points allocated to this strategy - /// active: a boolean to indice if this strategy is active or not + /// isActive: a boolean to indice if this strategy is active or not. This will be used as circuit-breaker for harvest() + /// and rebalance(), as a non-active strategy will not be neither harvested or rebalanced. /// cap: an optional cap in terms of deposited underlying asset. By default, it is set to 0(not activated) struct Strategy { uint120 allocated; uint120 allocationPoints; - bool active; uint120 cap; + StrategyStatus status; } /// @dev Euler saving rate struct @@ -39,6 +40,12 @@ interface IEulerAggregationVault { uint8 locked; } + enum StrategyStatus { + Inactive, + Active, + Emergency + } + function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external; function gulp() external; function harvest() external; diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index 753a9df6..b912e76b 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -25,4 +25,5 @@ library ErrorsLib { error InvalidAllocationPoints(); error CanNotRemoveStartegyWithAllocatedAmount(); error NoCapOnCashReserveStrategy(); + error CanNotAdjustAllocationPoints(); } diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index 1f8160b5..7749f143 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -20,19 +20,19 @@ abstract contract AllocationPointsModule is Shared { using SafeCast for uint256; /// @notice Adjust a certain strategy's allocation points. - /// @dev Can only be called by an address that have the ALLOCATIONS_MANAGER + /// @dev Can only be called by an address that have the STRATEGY_OPERATOR role. /// @param _strategy address of strategy /// @param _newPoints new strategy's points function adjustAllocationPoints(address _strategy, uint256 _newPoints) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - - if (_newPoints == 0) revert Errors.InvalidAllocationPoints(); IEulerAggregationVault.Strategy memory strategyDataCache = $.strategies[_strategy]; - if (!strategyDataCache.active) { - revert Errors.InactiveStrategy(); + if (strategyDataCache.status != IEulerAggregationVault.StrategyStatus.Active) { + revert Errors.CanNotAdjustAllocationPoints(); } + if (_newPoints == 0) revert Errors.InvalidAllocationPoints(); + $.strategies[_strategy].allocationPoints = _newPoints.toUint120(); $.totalAllocationPoints = $.totalAllocationPoints + _newPoints - strategyDataCache.allocationPoints; @@ -40,13 +40,14 @@ abstract contract AllocationPointsModule is Shared { } /// @notice Set cap on strategy allocated amount. - /// @dev By default, cap is set to 0, not activated. + /// @dev Can only be called by an address with the `GUARDIAN` role. + /// @dev By default, cap is set to 0. /// @param _strategy Strategy address. /// @param _cap Cap amount function setStrategyCap(address _strategy, uint256 _cap) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - if (!$.strategies[_strategy].active) { + if ($.strategies[_strategy].status != IEulerAggregationVault.StrategyStatus.Active) { revert Errors.InactiveStrategy(); } @@ -59,6 +60,22 @@ abstract contract AllocationPointsModule is Shared { emit Events.SetStrategyCap(_strategy, _cap); } + function activateStrategyEmergency(address _strategy) external virtual nonReentrant { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if ($.strategies[_strategy].status != IEulerAggregationVault.StrategyStatus.Active) revert(); + + $.strategies[_strategy].status = IEulerAggregationVault.StrategyStatus.Emergency; + } + + function deactivateStrategyEmergency(address _strategy) external virtual nonReentrant { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if ($.strategies[_strategy].status != IEulerAggregationVault.StrategyStatus.Emergency) revert(); + + $.strategies[_strategy].status = IEulerAggregationVault.StrategyStatus.Active; + } + /// @notice Add new strategy with it's allocation points. /// @dev Can only be called by an address that have STRATEGY_OPERATOR. /// @param _strategy Address of the strategy @@ -66,7 +83,7 @@ abstract contract AllocationPointsModule is Shared { function addStrategy(address _strategy, uint256 _allocationPoints) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - if ($.strategies[_strategy].active) { + if ($.strategies[_strategy].status != IEulerAggregationVault.StrategyStatus.Inactive) { revert Errors.StrategyAlreadyExist(); } @@ -81,7 +98,7 @@ abstract contract AllocationPointsModule is Shared { $.strategies[_strategy] = IEulerAggregationVault.Strategy({ allocated: 0, allocationPoints: _allocationPoints.toUint120(), - active: true, + status: IEulerAggregationVault.StrategyStatus.Active, cap: 0 }); @@ -92,17 +109,18 @@ abstract contract AllocationPointsModule is Shared { } /// @notice Remove strategy and set its allocation points to zero. - /// @dev This function does not pull funds, `harvest()` needs to be called to withdraw - /// @dev Can only be called by an address that have the STRATEGY_OPERATOR + /// @dev Can only be called by an address that have the STRATEGY_OPERATOR. + /// @dev This function does not pull funds nor harvest yield. + /// A faulty startegy that has an allocated amount can not be removed, instead it is advised + /// to set as a non-active strategy using the `setStrategyStatus()`. /// @param _strategy Address of the strategy function removeStrategy(address _strategy) external virtual nonReentrant { if (_strategy == address(0)) revert Errors.CanNotRemoveCashReserve(); AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - IEulerAggregationVault.Strategy storage strategyStorage = $.strategies[_strategy]; - if (!strategyStorage.active) { + if (strategyStorage.status == IEulerAggregationVault.StrategyStatus.Inactive) { revert Errors.AlreadyRemoved(); } if (strategyStorage.allocated > 0) revert Errors.CanNotRemoveStartegyWithAllocatedAmount(); @@ -110,9 +128,7 @@ abstract contract AllocationPointsModule is Shared { _callHooksTarget(REMOVE_STRATEGY, msg.sender); $.totalAllocationPoints -= strategyStorage.allocationPoints; - // we do not reset strategyStorage.allocated, as this will allow removing broken strategy - // and adding them later once fixed(circuit-breaker for harvesting and rebalancing). - strategyStorage.active = false; + strategyStorage.status = IEulerAggregationVault.StrategyStatus.Inactive; strategyStorage.allocationPoints = 0; strategyStorage.cap = 0; diff --git a/src/core/module/Rewards.sol b/src/core/module/Rewards.sol index 780fbdc0..53a862db 100644 --- a/src/core/module/Rewards.sol +++ b/src/core/module/Rewards.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; // interfaces import {IBalanceForwarder} from "../interface/IBalanceForwarder.sol"; +import {IEulerAggregationVault} from "../interface/IEulerAggregationVault.sol"; import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; @@ -24,7 +25,9 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { function optInStrategyRewards(address _strategy) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - if (!$.strategies[_strategy].active) revert Errors.InactiveStrategy(); + if ($.strategies[_strategy].status == IEulerAggregationVault.StrategyStatus.Inactive) { + revert Errors.InactiveStrategy(); + } IBalanceForwarder(_strategy).enableBalanceForwarder(); @@ -45,7 +48,9 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { function enableRewardForStrategy(address _strategy, address _reward) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - if (!$.strategies[_strategy].active) revert Errors.InactiveStrategy(); + if ($.strategies[_strategy].status == IEulerAggregationVault.StrategyStatus.Inactive) { + revert Errors.InactiveStrategy(); + } IRewardStreams(IBalanceForwarder(_strategy).balanceTrackerAddress()).enableReward(_strategy, _reward); @@ -63,7 +68,9 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - if (!$.strategies[_strategy].active) revert Errors.InactiveStrategy(); + if ($.strategies[_strategy].status == IEulerAggregationVault.StrategyStatus.Inactive) { + revert Errors.InactiveStrategy(); + } IRewardStreams(IBalanceForwarder(_strategy).balanceTrackerAddress()).disableReward( _strategy, _reward, _forfeitRecentReward diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index 82c5161d..4fae0ec3 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -78,13 +78,13 @@ contract EulerAggregationVaultBase is EVaultTestBase { withdrawalQueue = WithdrawalQueue(eulerAggregationVault.withdrawalQueue()); // grant admin roles to deployer - eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.GUARDIAN_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager - eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.GUARDIAN(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); @@ -105,12 +105,8 @@ contract EulerAggregationVaultBase is EVaultTestBase { assertEq(cashReserve.allocated, 0); assertEq(cashReserve.allocationPoints, CASH_RESERVE_ALLOCATION_POINTS); - assertEq(cashReserve.active, true); + assertEq(cashReserve.status == IEulerAggregationVault.StrategyStatus.Active, true); - assertEq( - eulerAggregationVault.getRoleAdmin(eulerAggregationVault.ALLOCATIONS_MANAGER()), - eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN() - ); assertEq( eulerAggregationVault.getRoleAdmin(eulerAggregationVault.STRATEGY_OPERATOR()), eulerAggregationVault.STRATEGY_OPERATOR_ADMIN() @@ -124,12 +120,10 @@ contract EulerAggregationVaultBase is EVaultTestBase { withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN() ); - assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN(), deployer)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_OPERATOR_ADMIN(), deployer)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); - assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.ALLOCATIONS_MANAGER(), manager)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_OPERATOR(), manager)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 08f75056..24caf529 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -47,13 +47,13 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { ); // grant admin roles to deployer - eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.GUARDIAN_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager - eulerAggregationVault.grantRole(eulerAggregationVault.ALLOCATIONS_MANAGER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.GUARDIAN(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager); withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); diff --git a/test/unit/AdjustAllocationPointsTest.t.sol b/test/unit/AdjustAllocationPointsTest.t.sol index 10b994f4..807da580 100644 --- a/test/unit/AdjustAllocationPointsTest.t.sol +++ b/test/unit/AdjustAllocationPointsTest.t.sol @@ -48,7 +48,7 @@ contract AdjustAllocationsPointsTest is EulerAggregationVaultBase { uint256 newAllocationPoints = 859e18; vm.startPrank(manager); - vm.expectRevert(ErrorsLib.InactiveStrategy.selector); + vm.expectRevert(ErrorsLib.CanNotAdjustAllocationPoints.selector); eulerAggregationVault.adjustAllocationPoints(address(eTST2), newAllocationPoints); vm.stopPrank(); } diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index d3be60de..28df2e1f 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -30,7 +30,7 @@ contract RemoveStrategyTest is EulerAggregationVaultBase { IEulerAggregationVault.Strategy memory strategyAfter = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(strategyAfter.active, false); + assertEq(strategyAfter.status == IEulerAggregationVault.StrategyStatus.Inactive, true); assertEq(strategyAfter.allocationPoints, 0); assertEq(eulerAggregationVault.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore - 1); @@ -50,7 +50,7 @@ contract RemoveStrategyTest is EulerAggregationVaultBase { IEulerAggregationVault.Strategy memory strategyAfter = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(strategyAfter.active, false); + assertEq(strategyAfter.status == IEulerAggregationVault.StrategyStatus.Inactive, true); assertEq(strategyAfter.allocationPoints, 0); assertEq(eulerAggregationVault.totalAllocationPoints(), totalAllocationPointsBefore - strategyAllocationPoints); assertEq(_getWithdrawalQueueLength(), withdrawalQueueLengthBefore - 1); From 20bfc1c67a60230d6c098660c5ea1e128adb48d0 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 11 Jul 2024 09:16:10 +0300 Subject: [PATCH 249/316] refactor --- src/core/EulerAggregationVault.sol | 12 ++---------- src/core/module/AllocationPoints.sol | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 618c367f..cd92c089 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -184,16 +184,8 @@ contract EulerAggregationVault is onlyRole(GUARDIAN) {} - /// @dev See {AllocationPointsModule-activateStrategyEmergency}. - function activateStrategyEmergency(address _strategy) - external - override - use(allocationPointsModule) - onlyRole(GUARDIAN) - {} - - /// @dev See {AllocationPointsModule-deactivateStrategyEmergency}. - function deactivateStrategyEmergency(address _strategy) + /// @dev See {AllocationPointsModule-toggleStrategyEmergencyStatus}. + function toggleStrategyEmergencyStatus(address _strategy) external override use(allocationPointsModule) diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index 7749f143..fec18f03 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -60,20 +60,21 @@ abstract contract AllocationPointsModule is Shared { emit Events.SetStrategyCap(_strategy, _cap); } - function activateStrategyEmergency(address _strategy) external virtual nonReentrant { + function toggleStrategyEmergencyStatus(address _strategy) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + IEulerAggregationVault.Strategy memory strategyCached = $.strategies[_strategy]; - if ($.strategies[_strategy].status != IEulerAggregationVault.StrategyStatus.Active) revert(); - - $.strategies[_strategy].status = IEulerAggregationVault.StrategyStatus.Emergency; - } - - function deactivateStrategyEmergency(address _strategy) external virtual nonReentrant { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + if (strategyCached.status == IEulerAggregationVault.StrategyStatus.Inactive) { + revert Errors.InactiveStrategy(); + } else if (strategyCached.status == IEulerAggregationVault.StrategyStatus.Active) { + strategyCached.status = IEulerAggregationVault.StrategyStatus.Emergency; - if ($.strategies[_strategy].status != IEulerAggregationVault.StrategyStatus.Emergency) revert(); + $.totalAllocationPoints -= strategyCached.allocationPoints; + } else { + strategyCached.status = IEulerAggregationVault.StrategyStatus.Active; - $.strategies[_strategy].status = IEulerAggregationVault.StrategyStatus.Active; + $.totalAllocationPoints += strategyCached.allocationPoints; + } } /// @notice Add new strategy with it's allocation points. From bafa48e176412d2edd0bbec431c9f66259fd7942 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 12 Jul 2024 09:54:05 +0300 Subject: [PATCH 250/316] deduct losses when set strategy in emergency mode --- src/core/EulerAggregationVault.sol | 35 ++-- src/core/common/Shared.sol | 23 ++- src/core/interface/IEulerAggregationVault.sol | 2 +- src/core/module/AllocationPoints.sol | 13 +- src/plugin/WithdrawalQueue.sol | 6 +- test/e2e/StrategyCapE2ETest.t.sol | 8 + ...ToggleStrategyEmergencyStatusE2ETest.t.sol | 179 ++++++++++++++++++ test/unit/AdjustAllocationPointsTest.t.sol | 14 +- 8 files changed, 237 insertions(+), 43 deletions(-) create mode 100644 test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index cd92c089..830af0fd 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -152,14 +152,6 @@ contract EulerAggregationVault is use(hooksModule) {} - /// @dev See {AllocationPointsModule-adjustAllocationPoints}. - function adjustAllocationPoints(address _strategy, uint256 _newPoints) - external - override - use(allocationPointsModule) - onlyRole(STRATEGY_OPERATOR) - {} - /// @dev See {AllocationPointsModule-addStrategy}. function addStrategy(address _strategy, uint256 _allocationPoints) external @@ -184,6 +176,14 @@ contract EulerAggregationVault is onlyRole(GUARDIAN) {} + /// @dev See {AllocationPointsModule-adjustAllocationPoints}. + function adjustAllocationPoints(address _strategy, uint256 _newPoints) + external + override + use(allocationPointsModule) + onlyRole(GUARDIAN) + {} + /// @dev See {AllocationPointsModule-toggleStrategyEmergencyStatus}. function toggleStrategyEmergencyStatus(address _strategy) external @@ -263,17 +263,21 @@ contract EulerAggregationVault is /// @dev Can only be called from the WithdrawalQueue contract. /// @param _strategy Strategy's address. /// @param _withdrawAmount Amount to withdraw. - function executeStrategyWithdraw(address _strategy, uint256 _withdrawAmount) external { + function executeStrategyWithdraw(address _strategy, uint256 _withdrawAmount) external returns (uint256) { _isCallerWithdrawalQueue(); AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + if ($.strategies[_strategy].status != IEulerAggregationVault.StrategyStatus.Active) return 0; + // Update allocated assets $.strategies[_strategy].allocated -= uint120(_withdrawAmount); $.totalAllocated -= _withdrawAmount; // Do actual withdraw from strategy IERC4626(_strategy).withdraw(_withdrawAmount, address(this), address(this)); + + return _withdrawAmount; } /// @notice Execute a withdraw from the AggregationVault @@ -524,18 +528,7 @@ contract EulerAggregationVault is $.totalAllocated = $.totalAllocated + totalYield - totalLoss; if (totalLoss > totalYield) { - uint256 netLoss = totalLoss - totalYield; - uint168 cachedInterestLeft = $.interestLeft; - - if (cachedInterestLeft >= netLoss) { - // cut loss from interest left only - cachedInterestLeft -= uint168(netLoss); - } else { - // cut the interest left and socialize the diff - $.totalAssetsDeposited -= netLoss - cachedInterestLeft; - cachedInterestLeft = 0; - } - $.interestLeft = cachedInterestLeft; + _deductLoss(totalLoss - totalYield); } emit Events.Harvest($.totalAllocated, totalYield, totalLoss); diff --git a/src/core/common/Shared.sol b/src/core/common/Shared.sol index ce91c75f..b7054802 100644 --- a/src/core/common/Shared.sol +++ b/src/core/common/Shared.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.0; // interfaces import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; // libs -import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {HooksLib} from "../lib/HooksLib.sol"; +import {StorageLib as Storage, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; /// @title Shared contract @@ -28,7 +28,7 @@ abstract contract Shared { uint256 constant HOOKS_MASK = 0x00000000000000000000000000000000000000000000000000000000FFFFFFFF; modifier nonReentrant() { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); if ($.locked == REENTRANCYLOCK__LOCKED) revert Errors.Reentrancy(); @@ -37,6 +37,21 @@ abstract contract Shared { $.locked = REENTRANCYLOCK__UNLOCKED; } + function _deductLoss(uint256 _lostAmount) internal { + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + + uint168 cachedInterestLeft = $.interestLeft; + if (cachedInterestLeft >= _lostAmount) { + // cut loss from interest left only + cachedInterestLeft -= uint168(_lostAmount); + } else { + // cut the interest left and socialize the diff + $.totalAssetsDeposited -= _lostAmount - cachedInterestLeft; + cachedInterestLeft = 0; + } + $.interestLeft = cachedInterestLeft; + } + function _setHooksConfig(address _hooksTarget, uint32 _hookedFns) internal { if (_hooksTarget != address(0) && IHookTarget(_hooksTarget).isHookTarget() != IHookTarget.isHookTarget.selector) { @@ -47,7 +62,7 @@ abstract contract Shared { } if (_hookedFns >= ACTIONS_COUNTER) revert Errors.InvalidHookedFns(); - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); $.hooksConfig = (uint256(uint160(_hooksTarget)) << 32) | uint256(_hookedFns); } @@ -55,7 +70,7 @@ abstract contract Shared { /// @param _fn Function to call the hook for. /// @param _caller Caller's address. function _callHooksTarget(uint32 _fn, address _caller) internal { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); (address target, uint32 hookedFns) = _getHooksConfig($.hooksConfig); diff --git a/src/core/interface/IEulerAggregationVault.sol b/src/core/interface/IEulerAggregationVault.sol index dfa36c75..f80a47db 100644 --- a/src/core/interface/IEulerAggregationVault.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -49,7 +49,7 @@ interface IEulerAggregationVault { function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external; function gulp() external; function harvest() external; - function executeStrategyWithdraw(address _strategy, uint256 _withdrawAmount) external; + function executeStrategyWithdraw(address _strategy, uint256 _withdrawAmount) external returns (uint256); function executeAggregationVaultWithdraw( address caller, address receiver, diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index fec18f03..c9298321 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -13,14 +13,14 @@ import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; import {EventsLib as Events} from "../lib/EventsLib.sol"; -/// @title FeeModule contract +/// @title AllocationPointsModule contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) abstract contract AllocationPointsModule is Shared { using SafeCast for uint256; /// @notice Adjust a certain strategy's allocation points. - /// @dev Can only be called by an address that have the STRATEGY_OPERATOR role. + /// @dev Can only be called by an address that have the GUARDIAN role. /// @param _strategy address of strategy /// @param _newPoints new strategy's points function adjustAllocationPoints(address _strategy, uint256 _newPoints) external virtual nonReentrant { @@ -31,8 +31,6 @@ abstract contract AllocationPointsModule is Shared { revert Errors.CanNotAdjustAllocationPoints(); } - if (_newPoints == 0) revert Errors.InvalidAllocationPoints(); - $.strategies[_strategy].allocationPoints = _newPoints.toUint120(); $.totalAllocationPoints = $.totalAllocationPoints + _newPoints - strategyDataCache.allocationPoints; @@ -67,11 +65,14 @@ abstract contract AllocationPointsModule is Shared { if (strategyCached.status == IEulerAggregationVault.StrategyStatus.Inactive) { revert Errors.InactiveStrategy(); } else if (strategyCached.status == IEulerAggregationVault.StrategyStatus.Active) { - strategyCached.status = IEulerAggregationVault.StrategyStatus.Emergency; + $.strategies[_strategy].status = IEulerAggregationVault.StrategyStatus.Emergency; $.totalAllocationPoints -= strategyCached.allocationPoints; + $.totalAllocated -= strategyCached.allocated; + + _deductLoss(strategyCached.allocated); } else { - strategyCached.status = IEulerAggregationVault.StrategyStatus.Active; + $.strategies[_strategy].status = IEulerAggregationVault.StrategyStatus.Active; $.totalAllocationPoints += strategyCached.allocationPoints; } diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index 07fbeba2..ce936f64 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -132,13 +132,11 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue uint256 desiredAssets = _assets - _availableAssets; uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; - IEulerAggregationVault(eulerAggregationVaultCached).executeStrategyWithdraw( + // update _availableAssets + _availableAssets += IEulerAggregationVault(eulerAggregationVaultCached).executeStrategyWithdraw( address(strategy), withdrawAmount ); - // update assetsRetrieved - _availableAssets += withdrawAmount; - if (_availableAssets >= _assets) { break; } diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index 01cc47c3..981904d1 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -46,6 +46,14 @@ contract StrategyCapE2ETest is EulerAggregationVaultBase { eulerAggregationVault.setStrategyCap(address(0x2), cap); } + function testSetCapForCashReserveStrategy() public { + uint256 cap = 1000000e18; + + vm.prank(manager); + vm.expectRevert(ErrorsLib.NoCapOnCashReserveStrategy.selector); + eulerAggregationVault.setStrategyCap(address(0), cap); + } + function testRebalanceAfterHittingCap() public { address[] memory strategiesToRebalance = new address[](1); diff --git a/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol new file mode 100644 index 00000000..18aa66ea --- /dev/null +++ b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import { + EulerAggregationVaultBase, + EulerAggregationVault, + console2, + EVault, + IEVault, + IRMTestDefault, + TestERC20, + IEulerAggregationVault, + ErrorsLib +} from "../common/EulerAggregationVaultBase.t.sol"; + +contract ToggleStrategyEmergencyStatusE2ETest is EulerAggregationVaultBase { + uint256 user1InitialBalance = 100000e18; + + IEVault eTSTsecondary; + + function setUp() public virtual override { + super.setUp(); + + uint256 initialStrategyAllocationPoints = 500e18; + _addStrategy(manager, address(eTST), initialStrategyAllocationPoints); + + { + eTSTsecondary = IEVault( + factory.createProxy( + address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount) + ) + ); + eTSTsecondary.setInterestRateModel(address(new IRMTestDefault())); + eTSTsecondary.setMaxLiquidationDiscount(0.2e4); + eTSTsecondary.setFeeReceiver(feeReceiver); + + _addStrategy(manager, address(eTSTsecondary), 1000e18); + } + + assetTST.mint(user1, user1InitialBalance); + } + + function testToggleStrategyEmergencyStatus() public { + uint256 totalAllocationPointsBefore = eulerAggregationVault.totalAllocationPoints(); + + vm.prank(manager); + eulerAggregationVault.toggleStrategyEmergencyStatus(address(eTSTsecondary)); + + IEulerAggregationVault.Strategy memory strategyAfter = eulerAggregationVault.getStrategy(address(eTSTsecondary)); + + assertEq(strategyAfter.status == IEulerAggregationVault.StrategyStatus.Emergency, true); + assertEq( + eulerAggregationVault.totalAllocationPoints(), totalAllocationPointsBefore - strategyAfter.allocationPoints + ); + + totalAllocationPointsBefore = eulerAggregationVault.totalAllocationPoints(); + + vm.prank(manager); + eulerAggregationVault.toggleStrategyEmergencyStatus(address(eTSTsecondary)); + + strategyAfter = eulerAggregationVault.getStrategy(address(eTSTsecondary)); + assertEq(strategyAfter.status == IEulerAggregationVault.StrategyStatus.Active, true); + assertEq( + eulerAggregationVault.totalAllocationPoints(), totalAllocationPointsBefore + strategyAfter.allocationPoints + ); + } + + function testToggleStrategyEmergencyStatusForInactiveStrategy() public { + vm.startPrank(manager); + vm.expectRevert(ErrorsLib.InactiveStrategy.selector); + eulerAggregationVault.toggleStrategyEmergencyStatus(address(0x2)); + vm.stopPrank(); + } + + // this to test a scneraio where a startegy `withdraw()` start reverting. + // Guardian will set the strategy in emergency mode, harvest and withdraw should execute, + // user will be able to withdraw from other strategy, losses will only be in the faulty strategy. + function testDepositRebalanceHarvestWIthdrawWithFaultyStartegy() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + // 2500 total points; 1000 for reserve(40%), 500(20%) for eTST, 1000(40%) for eTSTsecondary + // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary + vm.warp(block.timestamp + 86400); + { + IEulerAggregationVault.Strategy memory eTSTstrategyBefore = eulerAggregationVault.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory eTSTsecondarystrategyBefore = + eulerAggregationVault.getStrategy(address(eTSTsecondary)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), eTSTstrategyBefore.allocated); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), + eTSTsecondarystrategyBefore.allocated + ); + + uint256 expectedeTSTStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); + + assertTrue(expectedeTSTStrategyCash != 0); + assertTrue(expectedeTSTsecondaryStrategyCash != 0); + + address[] memory strategiesToRebalance = new address[](2); + strategiesToRebalance[0] = address(eTST); + strategiesToRebalance[1] = address(eTSTsecondary); + vm.prank(user1); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + + assertEq( + eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash + ); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedeTSTStrategyCash); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), + expectedeTSTsecondaryStrategyCash + ); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq( + (eulerAggregationVault.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + ); + assertEq( + assetTST.balanceOf(address(eulerAggregationVault)), + amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) + ); + } + + vm.warp(block.timestamp + 86400); + // mock an increase of aggregator balance due to yield + uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(eulerAggregationVault)); + uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); + uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; + uint256 eTSTYield = aggrNeweTSTUnderlyingBalance - aggrCurrenteTSTUnderlyingBalance; + + assetTST.mint(address(eTST), eTSTYield); + eTST.skim(type(uint256).max, address(eulerAggregationVault)); + + // set eTSTsecondary in emergency mode + vm.prank(manager); + eulerAggregationVault.toggleStrategyEmergencyStatus(address(eTSTsecondary)); + + // full withdraw, will have to withdraw from strategy as cash reserve is not enough + { + uint256 amountToWithdraw = eulerAggregationVault.balanceOf(user1); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + uint256 aggregatorTotalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + + vm.prank(user1); + eulerAggregationVault.redeem(amountToWithdraw, user1, user1); + + // assertEq(eTST.balanceOf(address(eulerAggregationVault)), 0); + // assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); + // assertEq(eulerAggregationVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); + // assertEq( + // assetTST.balanceOf(user1), + // user1AssetTSTBalanceBefore + eulerAggregationVault.convertToAssets(amountToWithdraw) + // ); + } + } +} diff --git a/test/unit/AdjustAllocationPointsTest.t.sol b/test/unit/AdjustAllocationPointsTest.t.sol index 807da580..346d1751 100644 --- a/test/unit/AdjustAllocationPointsTest.t.sol +++ b/test/unit/AdjustAllocationPointsTest.t.sol @@ -53,12 +53,12 @@ contract AdjustAllocationsPointsTest is EulerAggregationVaultBase { vm.stopPrank(); } - function testAdjustAllocationPoints_ZeroPoints() public { - uint256 newAllocationPoints = 0; + // function testAdjustAllocationPoints_ZeroPoints() public { + // uint256 newAllocationPoints = 0; - vm.startPrank(manager); - vm.expectRevert(ErrorsLib.InvalidAllocationPoints.selector); - eulerAggregationVault.adjustAllocationPoints(address(eTST), newAllocationPoints); - vm.stopPrank(); - } + // vm.startPrank(manager); + // vm.expectRevert(ErrorsLib.InvalidAllocationPoints.selector); + // eulerAggregationVault.adjustAllocationPoints(address(eTST), newAllocationPoints); + // vm.stopPrank(); + // } } From 1093fd8004215ef615460412caf8dd55e9c4a2cd Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 12 Jul 2024 10:22:26 +0300 Subject: [PATCH 251/316] fix --- src/core/module/AllocationPoints.sol | 10 ++++-- .../EulerAggregationLayerInvariants.t.sol | 33 +++++++++---------- .../handler/EulerAggregationVaultHandler.sol | 9 +++-- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index c9298321..dbc5c130 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -20,7 +20,7 @@ abstract contract AllocationPointsModule is Shared { using SafeCast for uint256; /// @notice Adjust a certain strategy's allocation points. - /// @dev Can only be called by an address that have the GUARDIAN role. + /// @dev Can only be called by an address that have the `GUARDIAN` role. /// @param _strategy address of strategy /// @param _newPoints new strategy's points function adjustAllocationPoints(address _strategy, uint256 _newPoints) external virtual nonReentrant { @@ -31,6 +31,10 @@ abstract contract AllocationPointsModule is Shared { revert Errors.CanNotAdjustAllocationPoints(); } + if (_strategy == address(0) && _newPoints == 0) { + revert Errors.InvalidAllocationPoints(); + } + $.strategies[_strategy].allocationPoints = _newPoints.toUint120(); $.totalAllocationPoints = $.totalAllocationPoints + _newPoints - strategyDataCache.allocationPoints; @@ -79,7 +83,7 @@ abstract contract AllocationPointsModule is Shared { } /// @notice Add new strategy with it's allocation points. - /// @dev Can only be called by an address that have STRATEGY_OPERATOR. + /// @dev Can only be called by an address that have `STRATEGY_OPERATOR` role. /// @param _strategy Address of the strategy /// @param _allocationPoints Strategy's allocation points function addStrategy(address _strategy, uint256 _allocationPoints) external virtual nonReentrant { @@ -111,7 +115,7 @@ abstract contract AllocationPointsModule is Shared { } /// @notice Remove strategy and set its allocation points to zero. - /// @dev Can only be called by an address that have the STRATEGY_OPERATOR. + /// @dev Can only be called by an address that have the `STRATEGY_OPERATOR` role. /// @dev This function does not pull funds nor harvest yield. /// A faulty startegy that has an allocated amount can not be removed, instead it is advised /// to set as a non-active strategy using the `setStrategyStatus()`. diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 5d6d182a..32f13b24 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -98,32 +98,29 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { assertEq(eulerAggregationVault.totalAllocationPoints(), expectedTotalAllocationpoints); } - // Every strategy in the withdrawal queue should have an allocation points > 0. - function invariant_withdrawalQueueStrategiesAllocationPoints() public view { + // (1) If withdrawal queue length == 0, then the total allocation points should be equal cash reserve allocation points. + // (2) If length > 0 and the total allocation points == cash reserve allocation points, then all other strategies should have a 0 allocation points. + // (3) withdrawal queue length should always be equal the ghost length variable. + function invariant_withdrawalQueue() public view { address withdrawalQueueAddr = eulerAggregationVault.withdrawalQueue(); (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); - for (uint256 i; i < withdrawalQueueLength; i++) { - assertGt(eulerAggregationVault.getStrategy(withdrawalQueueArray[i]).allocationPoints, 0); - } - } - - // If `total allocation points - cash reserve allocation points == 0`(no strategy added), the withdrawal queue length should be zero. - // Else, the length should be greater than zero. - function invariant_withdrawalQueue() public view { - address withdrawalQueueAddr = eulerAggregationVault.withdrawalQueue(); - - (, uint256 withdrawalQueueLength) = IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); - uint256 cashReserveAllocationPoints = (eulerAggregationVault.getStrategy(address(0))).allocationPoints; - if (eulerAggregationVault.totalAllocationPoints() - cashReserveAllocationPoints == 0) { - assertEq(withdrawalQueueLength, 0); - } else { - assertGt(withdrawalQueueLength, 0); + if (withdrawalQueueLength == 0) { + assertEq(eulerAggregationVault.totalAllocationPoints(), cashReserveAllocationPoints); } + + if (withdrawalQueueLength == 0 && eulerAggregationVault.totalAllocationPoints() == cashReserveAllocationPoints) + { + for (uint256 i; i < withdrawalQueueLength; i++) { + assertEq(eulerAggregationVault.getStrategy(withdrawalQueueArray[i]).allocationPoints, 0); + } + } + + assertEq(withdrawalQueueLength, eulerAggregationVaultHandler.ghost_withdrawalQueueLength()); } // total allocated amount should always be equal the sum of allocated amount in all the strategies. diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index 841eca59..93e50d9e 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -26,11 +26,12 @@ contract EulerAggregationVaultHandler is Test { WithdrawalQueue internal withdrawalQueue; // ghost vars - uint256 ghost_totalAllocationPoints; - uint256 ghost_totalAssetsDeposited; - mapping(address => uint256) ghost_allocationPoints; + uint256 public ghost_totalAllocationPoints; + uint256 public ghost_totalAssetsDeposited; + mapping(address => uint256) public ghost_allocationPoints; address[] public ghost_feeRecipients; mapping(address => uint256) public ghost_accumulatedPerformanceFeePerRecipient; + uint256 public ghost_withdrawalQueueLength; // last function call state address currentActor; @@ -131,6 +132,7 @@ contract EulerAggregationVaultHandler is Test { if (success) { ghost_totalAllocationPoints += _allocationPoints; ghost_allocationPoints[strategyAddr] = _allocationPoints; + ghost_withdrawalQueueLength += 1; } IEulerAggregationVault.Strategy memory strategyAfter = eulerAggVault.getStrategy(strategyAddr); assertEq(eulerAggVault.totalAllocationPoints(), ghost_totalAllocationPoints); @@ -151,6 +153,7 @@ contract EulerAggregationVaultHandler is Test { if (success) { ghost_totalAllocationPoints -= strategyBefore.allocationPoints; ghost_allocationPoints[strategyAddr] = 0; + ghost_withdrawalQueueLength -= 1; } IEulerAggregationVault.Strategy memory strategyAfter = eulerAggVault.getStrategy(strategyAddr); From 2abe33a1472137dcdec1d36fd10a02192f71bfe1 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 12 Jul 2024 11:10:47 +0300 Subject: [PATCH 252/316] refactor --- foundry.toml | 2 +- src/core/EulerAggregationVault.sol | 15 ++++---- src/core/EulerAggregationVaultFactory.sol | 36 +++++++------------ src/core/interface/IEulerAggregationVault.sol | 14 ++++++-- src/core/interface/IWithdrawalQueue.sol | 2 +- src/plugin/WithdrawalQueue.sol | 5 ++- test/A16zPropertyTests.t.sol | 3 +- test/common/EulerAggregationVaultBase.t.sol | 3 +- test/e2e/BalanceForwarderE2ETest.t.sol | 3 +- test/echidna/CryticERC4626TestsHarness.t.sol | 3 +- 10 files changed, 45 insertions(+), 41 deletions(-) diff --git a/foundry.toml b/foundry.toml index ddfeef13..8602ff6b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,7 +4,7 @@ out = "out" libs = ["lib"] test = 'test' optimizer = true -optimizer_runs = 500 +optimizer_runs = 300 solc = "0.8.24" gas_reports = ["*"] fs_permissions = [{ access = "read", path = "./"}] diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 830af0fd..29ff951e 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -54,12 +54,13 @@ contract EulerAggregationVault is uint256 public constant MIN_SHARES_FOR_GULP = 1e7; /// @dev Constructor. - /// @param _rewardsModule Address of Rewards module. - /// @param _hooksModule Address of Hooks module. - /// @param _feeModule Address of Fee module. - /// @param _allocationPointsModule Address of AllocationPoints module. - constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) - Dispatch(_rewardsModule, _hooksModule, _feeModule, _allocationPointsModule) + constructor(ConstructorParams memory _constructorParams) + Dispatch( + _constructorParams.rewardsModule, + _constructorParams.hooksModule, + _constructorParams.feeModule, + _constructorParams.allocationPointsModule + ) {} /// @notice Initialize the EulerAggregationVault. @@ -91,6 +92,8 @@ contract EulerAggregationVault is _setRoleAdmin(STRATEGY_OPERATOR, STRATEGY_OPERATOR_ADMIN); _setRoleAdmin(STRATEGY_OPERATOR, STRATEGY_OPERATOR_ADMIN); _setRoleAdmin(AGGREGATION_VAULT_MANAGER, AGGREGATION_VAULT_MANAGER_ADMIN); + + IWithdrawalQueue(_initParams.withdrawalQueuePlugin).init(_initParams.aggregationVaultOwner); } /// @dev See {FeeModule-setFeeRecipient}. diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index 36401fb4..6a6c2f52 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -2,10 +2,6 @@ pragma solidity ^0.8.0; // contracts -import {Rewards} from "./module/Rewards.sol"; -import {Hooks} from "./module/Hooks.sol"; -import {Fee} from "./module/Fee.sol"; -import {WithdrawalQueue} from "../plugin/WithdrawalQueue.sol"; import {EulerAggregationVault, IEulerAggregationVault} from "./EulerAggregationVault.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; // libs @@ -38,6 +34,7 @@ contract EulerAggregationVaultFactory is Ownable { /// @dev Init params struct. struct FactoryParams { + address owner; address balanceTracker; address rewardsModuleImpl; address hooksModuleImpl; @@ -52,15 +49,13 @@ contract EulerAggregationVaultFactory is Ownable { ); /// @notice Constructor. - /// @param _owner Factory owner. /// @param _factoryParams FactoryParams struct. - constructor(address _owner, FactoryParams memory _factoryParams) Ownable(_owner) { + constructor(FactoryParams memory _factoryParams) Ownable(_factoryParams.owner) { balanceTracker = _factoryParams.balanceTracker; rewardsModuleImpl = _factoryParams.rewardsModuleImpl; hooksModuleImpl = _factoryParams.hooksModuleImpl; feeModuleImpl = _factoryParams.feeModuleImpl; allocationPointsModuleImpl = _factoryParams.allocationPointsModuleImpl; - rebalancer = _factoryParams.rebalancer; } @@ -95,31 +90,26 @@ contract EulerAggregationVaultFactory is Ownable { ) external returns (address) { if (!isWhitelistedWithdrawalQueueImpl[_withdrawalQueueImpl]) revert NotWhitelistedWithdrawalQueueImpl(); - // cloning core modules - address rewardsModuleAddr = Clones.clone(rewardsModuleImpl); - address hooksModuleAddr = Clones.clone(hooksModuleImpl); - address feeModuleAddr = Clones.clone(feeModuleImpl); - address allocationpointsModuleAddr = Clones.clone(allocationPointsModuleImpl); - - // cloning plugins - WithdrawalQueue withdrawalQueue = WithdrawalQueue(Clones.clone(_withdrawalQueueImpl)); - // deploy new aggregation vault - EulerAggregationVault eulerAggregationVault = - new EulerAggregationVault(rewardsModuleAddr, hooksModuleAddr, feeModuleAddr, allocationpointsModuleAddr); + IEulerAggregationVault.ConstructorParams memory aggregationVaultConstructorParams = IEulerAggregationVault + .ConstructorParams({ + rewardsModule: Clones.clone(rewardsModuleImpl), + hooksModule: Clones.clone(hooksModuleImpl), + feeModule: Clones.clone(feeModuleImpl), + allocationPointsModule: Clones.clone(allocationPointsModuleImpl) + }); + EulerAggregationVault eulerAggregationVault = new EulerAggregationVault(aggregationVaultConstructorParams); IEulerAggregationVault.InitParams memory aggregationVaultInitParams = IEulerAggregationVault.InitParams({ - balanceTracker: balanceTracker, - withdrawalQueuePlugin: address(withdrawalQueue), - rebalancerPlugin: rebalancer, aggregationVaultOwner: msg.sender, asset: _asset, + withdrawalQueuePlugin: Clones.clone(_withdrawalQueueImpl), + rebalancerPlugin: rebalancer, + balanceTracker: balanceTracker, name: _name, symbol: _symbol, initialCashAllocationPoints: _initialCashAllocationPoints }); - - withdrawalQueue.init(msg.sender, address(eulerAggregationVault)); eulerAggregationVault.init(aggregationVaultInitParams); aggregationVaults.push(address(eulerAggregationVault)); diff --git a/src/core/interface/IEulerAggregationVault.sol b/src/core/interface/IEulerAggregationVault.sol index f80a47db..aad81434 100644 --- a/src/core/interface/IEulerAggregationVault.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -2,13 +2,21 @@ pragma solidity ^0.8.0; interface IEulerAggregationVault { + /// @dev Struct to pass to constrcutor. + struct ConstructorParams { + address rewardsModule; + address hooksModule; + address feeModule; + address allocationPointsModule; + } + /// @dev Struct to pass init() call params. struct InitParams { - address balanceTracker; - address withdrawalQueuePlugin; - address rebalancerPlugin; address aggregationVaultOwner; address asset; + address withdrawalQueuePlugin; + address rebalancerPlugin; + address balanceTracker; string name; string symbol; uint256 initialCashAllocationPoints; diff --git a/src/core/interface/IWithdrawalQueue.sol b/src/core/interface/IWithdrawalQueue.sol index d9f378ba..cf554c26 100644 --- a/src/core/interface/IWithdrawalQueue.sol +++ b/src/core/interface/IWithdrawalQueue.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; interface IWithdrawalQueue { - function init(address _owner, address _eulerAggregationVault) external; + function init(address _owner) external; function addStrategyToWithdrawalQueue(address _strategy) external; function removeStrategyFromWithdrawalQueue(address _strategy) external; function callWithdrawalQueue( diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index ce936f64..5d07bf38 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -38,10 +38,9 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue /// @notice Initialize WithdrawalQueue. /// @param _owner Aggregation vault owner. - /// @param _eulerAggregationVault Address of aggregation vault. - function init(address _owner, address _eulerAggregationVault) external initializer { + function init(address _owner) external initializer { WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - $.eulerAggregationVault = _eulerAggregationVault; + $.eulerAggregationVault = msg.sender; // Setup DEFAULT_ADMIN _grantRole(DEFAULT_ADMIN_ROLE, _owner); diff --git a/test/A16zPropertyTests.t.sol b/test/A16zPropertyTests.t.sol index b73c0eb7..3f12579e 100644 --- a/test/A16zPropertyTests.t.sol +++ b/test/A16zPropertyTests.t.sol @@ -44,6 +44,7 @@ contract A16zPropertyTests is ERC4626Test { withdrawalQueuePluginImpl = new WithdrawalQueue(); EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ + owner: factoryOwner, balanceTracker: address(0), rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), @@ -51,7 +52,7 @@ contract A16zPropertyTests is ERC4626Test { allocationPointsModuleImpl: address(allocationPointsModuleImpl), rebalancer: address(rebalancerPlugin) }); - eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryOwner, factoryParams); + eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); vm.prank(factoryOwner); eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueuePluginImpl)); diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index 4fae0ec3..fc9a9ada 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -56,6 +56,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { withdrawalQueueImpl = new WithdrawalQueue(); EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ + owner: deployer, balanceTracker: address(0), rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), @@ -63,7 +64,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { allocationPointsModuleImpl: address(allocationPointsModuleImpl), rebalancer: address(rebalancer) }); - eulerAggregationVaultFactory = new EulerAggregationVaultFactory(deployer, factoryParams); + eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueueImpl)); eulerAggregationVault = EulerAggregationVault( diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 24caf529..51d41383 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -26,6 +26,7 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { trackingReward = address(new TrackingRewardStreams(address(evc), 2 weeks)); EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ + owner: deployer, balanceTracker: trackingReward, rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), @@ -33,7 +34,7 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { allocationPointsModuleImpl: address(allocationPointsModuleImpl), rebalancer: address(rebalancer) }); - eulerAggregationVaultFactory = new EulerAggregationVaultFactory(deployer, factoryParams); + eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueueImpl)); eulerAggregationVault = EulerAggregationVault( diff --git a/test/echidna/CryticERC4626TestsHarness.t.sol b/test/echidna/CryticERC4626TestsHarness.t.sol index c645495c..3eafa605 100644 --- a/test/echidna/CryticERC4626TestsHarness.t.sol +++ b/test/echidna/CryticERC4626TestsHarness.t.sol @@ -41,6 +41,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { withdrawalQueuePluginImpl = new WithdrawalQueue(); EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ + owner: address(this), balanceTracker: address(0), rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), @@ -48,7 +49,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { allocationPointsModuleImpl: address(allocationPointsModuleImpl), rebalancer: address(rebalancerPlugin) }); - eulerAggregationVaultFactory = new EulerAggregationVaultFactory(address(this), factoryParams); + eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueuePluginImpl)); TestERC20Token _asset = new TestERC20Token("Test Token", "TT", 18); From 80b8a19fa6b77bc76cf9bf377606ca64e79aafbb Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 12 Jul 2024 19:06:10 +0300 Subject: [PATCH 253/316] improve invariants --- src/core/interface/IEulerAggregationVault.sol | 9 +++- src/core/lib/ErrorsLib.sol | 2 + src/core/module/AllocationPoints.sol | 11 ++++ src/plugin/WithdrawalQueue.sol | 2 +- .../EulerAggregationLayerInvariants.t.sol | 28 +++++++--- .../handler/EulerAggregationVaultHandler.sol | 52 ++++++++++++++++++- test/unit/AdjustAllocationPointsTest.t.sol | 14 ++--- 7 files changed, 98 insertions(+), 20 deletions(-) diff --git a/src/core/interface/IEulerAggregationVault.sol b/src/core/interface/IEulerAggregationVault.sol index aad81434..d35b07b5 100644 --- a/src/core/interface/IEulerAggregationVault.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -25,9 +25,8 @@ interface IEulerAggregationVault { /// @dev A struct that hold a strategy allocation's config /// allocated: amount of asset deposited into strategy /// allocationPoints: number of points allocated to this strategy - /// isActive: a boolean to indice if this strategy is active or not. This will be used as circuit-breaker for harvest() - /// and rebalance(), as a non-active strategy will not be neither harvested or rebalanced. /// cap: an optional cap in terms of deposited underlying asset. By default, it is set to 0(not activated) + /// status: an enum describing the strategy status. Check the enum definition for more details. struct Strategy { uint120 allocated; uint120 allocationPoints; @@ -48,6 +47,12 @@ interface IEulerAggregationVault { uint8 locked; } + /// @dev An enum for strategy status. + /// An inactive strategy is a strategy that is not added to and recognized by the withdrawal queue. + /// An active startegy is a well-functional strategy that is added in the withdrawal queue, can be rebalanced and harvested. + /// A strategy status set as Emeregncy, is when the strategy for some reasons can no longer be withdrawn from or deposited it, + /// this will be used as a circuit-breaker to ensure that the aggregation vault can continue functioning as intended, + /// and the only impacted strategy will be the one set as Emergency. enum StrategyStatus { Inactive, Active, diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index b912e76b..db269c4c 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -26,4 +26,6 @@ library ErrorsLib { error CanNotRemoveStartegyWithAllocatedAmount(); error NoCapOnCashReserveStrategy(); error CanNotAdjustAllocationPoints(); + error CanNotToggleStrategyEmergencyStatus(); + error CanNotRemoveEmergencyStrategy(); } diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index dbc5c130..ce0c57fd 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -62,7 +62,14 @@ abstract contract AllocationPointsModule is Shared { emit Events.SetStrategyCap(_strategy, _cap); } + /// @dev Toggle a strategy status between `Active` and `Emergency`. + /// @dev Can only get called by an address with the `GUARDIAN` role. + /// @dev This should be used as a cricuit-breaker to exclude a faulty strategy from being harvest or rebalanced. + /// It also deduct all the deposited amounts into the strategy as loss, and uses a loss socialization mechanism. + /// This is needed, in case the aggregation vault can no longer withdraw from a certain strategy. function toggleStrategyEmergencyStatus(address _strategy) external virtual nonReentrant { + if (_strategy == address(0)) revert Errors.CanNotToggleStrategyEmergencyStatus(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); IEulerAggregationVault.Strategy memory strategyCached = $.strategies[_strategy]; @@ -79,6 +86,7 @@ abstract contract AllocationPointsModule is Shared { $.strategies[_strategy].status = IEulerAggregationVault.StrategyStatus.Active; $.totalAllocationPoints += strategyCached.allocationPoints; + $.totalAllocated += strategyCached.allocated; } } @@ -129,6 +137,9 @@ abstract contract AllocationPointsModule is Shared { if (strategyStorage.status == IEulerAggregationVault.StrategyStatus.Inactive) { revert Errors.AlreadyRemoved(); } + if (strategyStorage.status == IEulerAggregationVault.StrategyStatus.Emergency) { + revert Errors.CanNotRemoveEmergencyStrategy(); + } if (strategyStorage.allocated > 0) revert Errors.CanNotRemoveStartegyWithAllocatedAmount(); _callHooksTarget(REMOVE_STRATEGY, msg.sender); diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index 5d07bf38..a4afe6f6 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -142,7 +142,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue } } - // is this possible? + //TODO: is this even possible now? if (_availableAssets < _assets) { revert NotEnoughAssets(); } diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 32f13b24..4a2b7f61 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -6,7 +6,8 @@ import { EulerAggregationVault, IWithdrawalQueue, IEVault, - TestERC20 + TestERC20, + IEulerAggregationVault } from "../common/EulerAggregationVaultBase.t.sol"; import {Actor} from "./util/Actor.sol"; import {Strategy} from "./util/Strategy.sol"; @@ -91,15 +92,19 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { uint256 expectedTotalAllocationpoints; expectedTotalAllocationpoints += (eulerAggregationVault.getStrategy(address(0))).allocationPoints; for (uint256 i; i < withdrawalQueueLength; i++) { - expectedTotalAllocationpoints += - (eulerAggregationVault.getStrategy(withdrawalQueueArray[i])).allocationPoints; + IEulerAggregationVault.Strategy memory strategy = eulerAggregationVault.getStrategy(withdrawalQueueArray[i]); + + if (strategy.status == IEulerAggregationVault.StrategyStatus.Active) { + expectedTotalAllocationpoints += + strategy.allocationPoints; + } } - assertEq(eulerAggregationVault.totalAllocationPoints(), expectedTotalAllocationpoints); + // assertEq(eulerAggregationVault.totalAllocationPoints(), expectedTotalAllocationpoints); } // (1) If withdrawal queue length == 0, then the total allocation points should be equal cash reserve allocation points. - // (2) If length > 0 and the total allocation points == cash reserve allocation points, then all other strategies should have a 0 allocation points. + // (2) If length > 0 and the total allocation points == cash reserve allocation points, then every strategy should have a 0 allocation points or should be a strategy in EMERGENCY mode. // (3) withdrawal queue length should always be equal the ghost length variable. function invariant_withdrawalQueue() public view { address withdrawalQueueAddr = eulerAggregationVault.withdrawalQueue(); @@ -113,10 +118,13 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { assertEq(eulerAggregationVault.totalAllocationPoints(), cashReserveAllocationPoints); } - if (withdrawalQueueLength == 0 && eulerAggregationVault.totalAllocationPoints() == cashReserveAllocationPoints) + if (withdrawalQueueLength > 0 && eulerAggregationVault.totalAllocationPoints() == cashReserveAllocationPoints) { for (uint256 i; i < withdrawalQueueLength; i++) { - assertEq(eulerAggregationVault.getStrategy(withdrawalQueueArray[i]).allocationPoints, 0); + IEulerAggregationVault.Strategy memory strategy = eulerAggregationVault.getStrategy(withdrawalQueueArray[i]); + assertEq( + strategy.allocationPoints == 0 || strategy.status == IEulerAggregationVault.StrategyStatus.Emergency, true + ); } } @@ -132,7 +140,11 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { uint256 aggregatedAllocatedAmount; for (uint256 i; i < withdrawalQueueLength; i++) { - aggregatedAllocatedAmount += (eulerAggregationVault.getStrategy(withdrawalQueueArray[i])).allocated; + IEulerAggregationVault.Strategy memory strategy = eulerAggregationVault.getStrategy(withdrawalQueueArray[i]); + + if (strategy.status == IEulerAggregationVault.StrategyStatus.Active) { + aggregatedAllocatedAmount += strategy.allocated; + } } assertEq(eulerAggregationVault.totalAllocated(), aggregatedAllocatedAmount); diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index 93e50d9e..9fc9fbdb 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -119,6 +119,31 @@ contract EulerAggregationVaultHandler is Test { assertEq(strategyAfter.cap, strategyBefore.cap); } } + + function toggleStrategyEmergencyStatus(uint256 _strategyIndexSeed) external { + address strategyAddr = strategyUtil.fetchStrategy(_strategyIndexSeed); + + // simulating loss when switching strategy to emergency status + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggVault.getStrategy(strategyAddr); + uint256 cached_ghost_totalAssetsDeposited = _simulateLossSocialization(strategyBefore.allocated); + + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( + 0, + address(eulerAggVault), + abi.encodeWithSelector(EulerAggregationVault.toggleStrategyEmergencyStatus.selector, strategyAddr) + ); + + IEulerAggregationVault.Strategy memory strategyAfter = eulerAggVault.getStrategy(strategyAddr); + if (success) { + if (strategyAfter.status == IEulerAggregationVault.StrategyStatus.Emergency) { + ghost_totalAllocationPoints -= strategyAfter.allocationPoints; + ghost_totalAssetsDeposited = cached_ghost_totalAssetsDeposited; + } else if (strategyAfter.status == IEulerAggregationVault.StrategyStatus.Active) { + ghost_totalAllocationPoints += strategyAfter.allocationPoints; + ghost_totalAssetsDeposited += strategyAfter.allocated; + } + } + } function addStrategy(uint256 _strategyIndexSeed, uint256 _allocationPoints) external { address strategyAddr = strategyUtil.fetchStrategy(_strategyIndexSeed); @@ -162,6 +187,9 @@ contract EulerAggregationVaultHandler is Test { } function harvest(uint256 _actorIndexSeed) external { + // track total yield and total loss to simulate loss socialization + uint256 totalYield; + uint256 totalLoss; // check if performance fee is on; store received fee per recipient if call is succesfull (address feeRecipient, uint256 performanceFee) = eulerAggVault.performanceFeeConfig(); uint256 accumulatedPerformanceFee; @@ -173,19 +201,27 @@ contract EulerAggregationVaultHandler is Test { for (uint256 i; i < withdrawalQueueLength; i++) { uint256 allocated = (eulerAggVault.getStrategy(withdrawalQueueArray[i])).allocated; uint256 underlying = IERC4626(withdrawalQueueArray[i]).maxWithdraw(address(eulerAggVault)); - if (underlying > allocated) { + if (underlying >= allocated) { uint256 yield = underlying - allocated; - accumulatedPerformanceFee += Math.mulDiv(yield, performanceFee, 1e18, Math.Rounding.Floor); + uint256 performancefee = Math.mulDiv(yield, performanceFee, 1e18, Math.Rounding.Floor); + accumulatedPerformanceFee += performancefee; + totalYield += yield - performancefee; + } else { + totalLoss +=allocated - underlying; } } } + uint256 cached_ghost_totalAssetsDeposited = _simulateLossSocialization(totalLoss); + + // simulate loss (, success, returnData) = actorUtil.initiateActorCall( _actorIndexSeed, address(eulerAggVault), abi.encodeWithSelector(IEulerAggregationVault.harvest.selector) ); if (success) { ghost_accumulatedPerformanceFeePerRecipient[feeRecipient] = accumulatedPerformanceFee; + ghost_totalAssetsDeposited = cached_ghost_totalAssetsDeposited; } } @@ -298,4 +334,16 @@ contract EulerAggregationVaultHandler is Test { vm.prank(_actor); asset.approve(address(eulerAggVault), _amount); } + + // This function simulate loss socialization, the part related to decreasing totalAssetsDeposited only. + // Return the expected ghost_totalAssetsDeposited after loss socialization. + function _simulateLossSocialization(uint256 _loss) private view returns (uint256) { + uint256 cached_ghost_totalAssetsDeposited = ghost_totalAssetsDeposited; + IEulerAggregationVault.AggregationVaultSavingRate memory avsr = eulerAggVault.getAggregationVaultSavingRate(); + if (_loss > avsr.interestLeft) { + cached_ghost_totalAssetsDeposited -= _loss - avsr.interestLeft; + } + + return cached_ghost_totalAssetsDeposited; + } } diff --git a/test/unit/AdjustAllocationPointsTest.t.sol b/test/unit/AdjustAllocationPointsTest.t.sol index 346d1751..c3c2a3e3 100644 --- a/test/unit/AdjustAllocationPointsTest.t.sol +++ b/test/unit/AdjustAllocationPointsTest.t.sol @@ -53,12 +53,12 @@ contract AdjustAllocationsPointsTest is EulerAggregationVaultBase { vm.stopPrank(); } - // function testAdjustAllocationPoints_ZeroPoints() public { - // uint256 newAllocationPoints = 0; + function testAdjustCashReserveAllocationPoints_ZeroPoints() public { + uint256 newAllocationPoints = 0; - // vm.startPrank(manager); - // vm.expectRevert(ErrorsLib.InvalidAllocationPoints.selector); - // eulerAggregationVault.adjustAllocationPoints(address(eTST), newAllocationPoints); - // vm.stopPrank(); - // } + vm.startPrank(manager); + vm.expectRevert(ErrorsLib.InvalidAllocationPoints.selector); + eulerAggregationVault.adjustAllocationPoints(address(0), newAllocationPoints); + vm.stopPrank(); + } } From cff5495c5946b4723eb7bbb296bc6cfe98decb9f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 12 Jul 2024 19:08:30 +0300 Subject: [PATCH 254/316] lint --- test/invariant/EulerAggregationLayerInvariants.t.sol | 12 ++++++------ .../handler/EulerAggregationVaultHandler.sol | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 4a2b7f61..7e691ead 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -95,8 +95,7 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { IEulerAggregationVault.Strategy memory strategy = eulerAggregationVault.getStrategy(withdrawalQueueArray[i]); if (strategy.status == IEulerAggregationVault.StrategyStatus.Active) { - expectedTotalAllocationpoints += - strategy.allocationPoints; + expectedTotalAllocationpoints += strategy.allocationPoints; } } @@ -118,12 +117,13 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { assertEq(eulerAggregationVault.totalAllocationPoints(), cashReserveAllocationPoints); } - if (withdrawalQueueLength > 0 && eulerAggregationVault.totalAllocationPoints() == cashReserveAllocationPoints) - { + if (withdrawalQueueLength > 0 && eulerAggregationVault.totalAllocationPoints() == cashReserveAllocationPoints) { for (uint256 i; i < withdrawalQueueLength; i++) { - IEulerAggregationVault.Strategy memory strategy = eulerAggregationVault.getStrategy(withdrawalQueueArray[i]); + IEulerAggregationVault.Strategy memory strategy = + eulerAggregationVault.getStrategy(withdrawalQueueArray[i]); assertEq( - strategy.allocationPoints == 0 || strategy.status == IEulerAggregationVault.StrategyStatus.Emergency, true + strategy.allocationPoints == 0 || strategy.status == IEulerAggregationVault.StrategyStatus.Emergency, + true ); } } diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index 9fc9fbdb..c96cfbe7 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -119,7 +119,7 @@ contract EulerAggregationVaultHandler is Test { assertEq(strategyAfter.cap, strategyBefore.cap); } } - + function toggleStrategyEmergencyStatus(uint256 _strategyIndexSeed) external { address strategyAddr = strategyUtil.fetchStrategy(_strategyIndexSeed); @@ -207,7 +207,7 @@ contract EulerAggregationVaultHandler is Test { accumulatedPerformanceFee += performancefee; totalYield += yield - performancefee; } else { - totalLoss +=allocated - underlying; + totalLoss += allocated - underlying; } } } From 3b2ff39248c7edc75bb2d326906d8231372cbf6a Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 12 Jul 2024 20:18:18 +0300 Subject: [PATCH 255/316] more tests --- src/core/lib/ErrorsLib.sol | 2 +- src/core/module/AllocationPoints.sol | 2 +- ...ToggleStrategyEmergencyStatusE2ETest.t.sol | 16 +++++++ test/unit/RemoveStrategy.t.sol | 47 +++++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index db269c4c..828baf67 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -27,5 +27,5 @@ library ErrorsLib { error NoCapOnCashReserveStrategy(); error CanNotAdjustAllocationPoints(); error CanNotToggleStrategyEmergencyStatus(); - error CanNotRemoveEmergencyStrategy(); + error CanNotRemoveStrategyInEmergencyStatus(); } diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/AllocationPoints.sol index ce0c57fd..ad1442e0 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/AllocationPoints.sol @@ -138,7 +138,7 @@ abstract contract AllocationPointsModule is Shared { revert Errors.AlreadyRemoved(); } if (strategyStorage.status == IEulerAggregationVault.StrategyStatus.Emergency) { - revert Errors.CanNotRemoveEmergencyStrategy(); + revert Errors.CanNotRemoveStrategyInEmergencyStatus(); } if (strategyStorage.allocated > 0) revert Errors.CanNotRemoveStartegyWithAllocatedAmount(); diff --git a/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol index 18aa66ea..61c23dba 100644 --- a/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol +++ b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol @@ -176,4 +176,20 @@ contract ToggleStrategyEmergencyStatusE2ETest is EulerAggregationVaultBase { // ); } } + + function testToggleCashReserveStrategyStatus() public { + vm.startPrank(manager); + vm.expectRevert(ErrorsLib.CanNotToggleStrategyEmergencyStatus.selector); + eulerAggregationVault.toggleStrategyEmergencyStatus(address(0)); + vm.stopPrank(); + } + + function testRemoveStrategyInEmergencyStatus() public { + vm.prank(manager); + eulerAggregationVault.toggleStrategyEmergencyStatus(address(eTSTsecondary)); + + vm.prank(manager); + vm.expectRevert(ErrorsLib.CanNotRemoveStrategyInEmergencyStatus.selector); + eulerAggregationVault.removeStrategy(address(eTSTsecondary)); + } } diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index 28df2e1f..1cd61109 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -123,4 +123,51 @@ contract RemoveStrategyTest is EulerAggregationVaultBase { vm.expectRevert(ErrorsLib.CanNotRemoveCashReserve.selector); eulerAggregationVault.removeStrategy(address(0)); } + + function testRemoveStrategyWithAllocatedAmount() public { + uint256 amountToDeposit = 10000e18; + assetTST.mint(user1, amountToDeposit); + + // deposit into aggregator + { + uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); + uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + uint256 userAssetBalanceBefore = assetTST.balanceOf(user1); + + vm.startPrank(user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); + vm.stopPrank(); + + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); + assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + } + + // rebalance into strategy + vm.warp(block.timestamp + 86400); + { + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggregationVault.getStrategy(address(eTST)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); + + uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * strategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); + + vm.prank(user1); + address[] memory strategiesToRebalance = new address[](1); + strategiesToRebalance[0] = address(eTST); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + + assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedStrategyCash); + } + + vm.prank(manager); + vm.expectRevert(ErrorsLib.CanNotRemoveStartegyWithAllocatedAmount.selector); + eulerAggregationVault.removeStrategy(address(eTST)); + } } From 599a5a24c3404bdabc9d2a8c5f62ad0505d79676 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:56:39 +0300 Subject: [PATCH 256/316] rename to StrategyModule; add tests --- src/core/Dispatch.sol | 12 +++--- src/core/EulerAggregationVault.sol | 32 ++++++---------- src/core/EulerAggregationVaultFactory.sol | 8 ++-- src/core/interface/IEulerAggregationVault.sol | 2 +- .../{AllocationPoints.sol => Strategy.sol} | 6 +-- test/A16zPropertyTests.t.sol | 8 ++-- test/common/EulerAggregationVaultBase.t.sol | 10 ++--- test/e2e/BalanceForwarderE2ETest.t.sol | 2 +- ...ToggleStrategyEmergencyStatusE2ETest.t.sol | 37 +++++++------------ test/echidna/CryticERC4626TestsHarness.t.sol | 8 ++-- 10 files changed, 52 insertions(+), 73 deletions(-) rename src/core/module/{AllocationPoints.sol => Strategy.sol} (97%) diff --git a/src/core/Dispatch.sol b/src/core/Dispatch.sol index 408c31ce..ec0518af 100644 --- a/src/core/Dispatch.sol +++ b/src/core/Dispatch.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import {Shared} from "./common/Shared.sol"; import {HooksModule} from "./module/Hooks.sol"; import {RewardsModule} from "./module/Rewards.sol"; -import {AllocationPointsModule} from "./module/AllocationPoints.sol"; +import {StrategyModule} from "./module/Strategy.sol"; import {FeeModule} from "./module/Fee.sol"; /// @title Dispatch contract @@ -13,22 +13,22 @@ import {FeeModule} from "./module/Fee.sol"; /// @author Euler Labs (https://www.eulerlabs.com/) /// @dev This contract implement the modifier to use for forwarding calls to a specific module using delegateCall. /// @dev Copied from https://github.com/euler-xyz/euler-vault-kit/blob/55d1a1fd7d572372f1c8b9f58aba0604bda3ca4f/src/EVault/Dispatch.sol. -abstract contract Dispatch is RewardsModule, HooksModule, FeeModule, AllocationPointsModule { +abstract contract Dispatch is RewardsModule, HooksModule, FeeModule, StrategyModule { address public immutable rewardsModule; address public immutable hooksModule; address public immutable feeModule; - address public immutable allocationPointsModule; + address public immutable strategyModule; /// @dev Constructor. /// @param _rewardsModule Address of Rewards module. /// @param _hooksModule Address of Hooks module. /// @param _feeModule Address of Fee module. - /// @param _allocationPointsModule Address of AllocationPoints module. - constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _allocationPointsModule) { + /// @param _strategyModule Address of Strategy module. + constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _strategyModule) { rewardsModule = _rewardsModule; hooksModule = _hooksModule; feeModule = _feeModule; - allocationPointsModule = _allocationPointsModule; + strategyModule = _strategyModule; } // Modifier proxies the function call to a module and low-level returns the result diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 29ff951e..44cb2474 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -59,7 +59,7 @@ contract EulerAggregationVault is _constructorParams.rewardsModule, _constructorParams.hooksModule, _constructorParams.feeModule, - _constructorParams.allocationPointsModule + _constructorParams.strategyModule ) {} @@ -155,43 +155,33 @@ contract EulerAggregationVault is use(hooksModule) {} - /// @dev See {AllocationPointsModule-addStrategy}. + /// @dev See {StrategyModule-addStrategy}. function addStrategy(address _strategy, uint256 _allocationPoints) external override - use(allocationPointsModule) + use(strategyModule) onlyRole(STRATEGY_OPERATOR) {} - /// @dev See {AllocationPointsModule-removeStrategy}. - function removeStrategy(address _strategy) - external - override - use(allocationPointsModule) - onlyRole(STRATEGY_OPERATOR) - {} + /// @dev See {StrategyModule-removeStrategy}. + function removeStrategy(address _strategy) external override use(strategyModule) onlyRole(STRATEGY_OPERATOR) {} - /// @dev See {AllocationPointsModule-setStrategyCap}. - function setStrategyCap(address _strategy, uint256 _cap) - external - override - use(allocationPointsModule) - onlyRole(GUARDIAN) - {} + /// @dev See {StrategyModule-setStrategyCap}. + function setStrategyCap(address _strategy, uint256 _cap) external override use(strategyModule) onlyRole(GUARDIAN) {} - /// @dev See {AllocationPointsModule-adjustAllocationPoints}. + /// @dev See {StrategyModule-adjustAllocationPoints}. function adjustAllocationPoints(address _strategy, uint256 _newPoints) external override - use(allocationPointsModule) + use(strategyModule) onlyRole(GUARDIAN) {} - /// @dev See {AllocationPointsModule-toggleStrategyEmergencyStatus}. + /// @dev See {StrategyModule-toggleStrategyEmergencyStatus}. function toggleStrategyEmergencyStatus(address _strategy) external override - use(allocationPointsModule) + use(strategyModule) onlyRole(GUARDIAN) {} diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index 6a6c2f52..7506fcce 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -21,7 +21,7 @@ contract EulerAggregationVaultFactory is Ownable { address public immutable rewardsModuleImpl; address public immutable hooksModuleImpl; address public immutable feeModuleImpl; - address public immutable allocationPointsModuleImpl; + address public immutable strategyModuleImpl; /// plugins /// @dev Rebalancer plugin contract address, one instance can serve different aggregation vaults address public immutable rebalancer; @@ -39,7 +39,7 @@ contract EulerAggregationVaultFactory is Ownable { address rewardsModuleImpl; address hooksModuleImpl; address feeModuleImpl; - address allocationPointsModuleImpl; + address strategyModuleImpl; address rebalancer; } @@ -55,7 +55,7 @@ contract EulerAggregationVaultFactory is Ownable { rewardsModuleImpl = _factoryParams.rewardsModuleImpl; hooksModuleImpl = _factoryParams.hooksModuleImpl; feeModuleImpl = _factoryParams.feeModuleImpl; - allocationPointsModuleImpl = _factoryParams.allocationPointsModuleImpl; + strategyModuleImpl = _factoryParams.strategyModuleImpl; rebalancer = _factoryParams.rebalancer; } @@ -96,7 +96,7 @@ contract EulerAggregationVaultFactory is Ownable { rewardsModule: Clones.clone(rewardsModuleImpl), hooksModule: Clones.clone(hooksModuleImpl), feeModule: Clones.clone(feeModuleImpl), - allocationPointsModule: Clones.clone(allocationPointsModuleImpl) + strategyModule: Clones.clone(strategyModuleImpl) }); EulerAggregationVault eulerAggregationVault = new EulerAggregationVault(aggregationVaultConstructorParams); diff --git a/src/core/interface/IEulerAggregationVault.sol b/src/core/interface/IEulerAggregationVault.sol index d35b07b5..3440d430 100644 --- a/src/core/interface/IEulerAggregationVault.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -7,7 +7,7 @@ interface IEulerAggregationVault { address rewardsModule; address hooksModule; address feeModule; - address allocationPointsModule; + address strategyModule; } /// @dev Struct to pass init() call params. diff --git a/src/core/module/AllocationPoints.sol b/src/core/module/Strategy.sol similarity index 97% rename from src/core/module/AllocationPoints.sol rename to src/core/module/Strategy.sol index ad1442e0..3d490f87 100644 --- a/src/core/module/AllocationPoints.sol +++ b/src/core/module/Strategy.sol @@ -13,10 +13,10 @@ import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; import {EventsLib as Events} from "../lib/EventsLib.sol"; -/// @title AllocationPointsModule contract +/// @title StrategyModule contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) -abstract contract AllocationPointsModule is Shared { +abstract contract StrategyModule is Shared { using SafeCast for uint256; /// @notice Adjust a certain strategy's allocation points. @@ -156,4 +156,4 @@ abstract contract AllocationPointsModule is Shared { } } -contract AllocationPoints is AllocationPointsModule {} +contract Strategy is StrategyModule {} diff --git a/test/A16zPropertyTests.t.sol b/test/A16zPropertyTests.t.sol index 3f12579e..cd243e3d 100644 --- a/test/A16zPropertyTests.t.sol +++ b/test/A16zPropertyTests.t.sol @@ -11,7 +11,7 @@ import {Rewards} from "../src/core/module/Rewards.sol"; import {Fee} from "../src/core/module/Fee.sol"; import {EulerAggregationVaultFactory} from "../src/core/EulerAggregationVaultFactory.sol"; import {WithdrawalQueue} from "../src/plugin/WithdrawalQueue.sol"; -import {AllocationPoints} from "../src/core/module/AllocationPoints.sol"; +import {Strategy} from "../src/core/module/Strategy.sol"; // mocks import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol"; @@ -24,7 +24,7 @@ contract A16zPropertyTests is ERC4626Test { Rewards rewardsImpl; Hooks hooksImpl; Fee feeModuleImpl; - AllocationPoints allocationPointsModuleImpl; + Strategy strategyModuleImpl; // plugins Rebalancer rebalancerPlugin; WithdrawalQueue withdrawalQueuePluginImpl; @@ -38,7 +38,7 @@ contract A16zPropertyTests is ERC4626Test { rewardsImpl = new Rewards(); hooksImpl = new Hooks(); feeModuleImpl = new Fee(); - allocationPointsModuleImpl = new AllocationPoints(); + strategyModuleImpl = new Strategy(); rebalancerPlugin = new Rebalancer(); withdrawalQueuePluginImpl = new WithdrawalQueue(); @@ -49,7 +49,7 @@ contract A16zPropertyTests is ERC4626Test { rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), - allocationPointsModuleImpl: address(allocationPointsModuleImpl), + strategyModuleImpl: address(strategyModuleImpl), rebalancer: address(rebalancerPlugin) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index fc9a9ada..ed99ec1f 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -13,7 +13,7 @@ import {Rewards} from "../../src/core/module/Rewards.sol"; import {Fee} from "../../src/core/module/Fee.sol"; import {EulerAggregationVaultFactory} from "../../src/core/EulerAggregationVaultFactory.sol"; import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; -import {AllocationPoints} from "../../src/core/module/AllocationPoints.sol"; +import {Strategy} from "../../src/core/module/Strategy.sol"; // libs import {ErrorsLib} from "../../src/core/lib/ErrorsLib.sol"; @@ -29,7 +29,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { Rewards rewardsImpl; Hooks hooksImpl; Fee feeModuleImpl; - AllocationPoints allocationPointsModuleImpl; + Strategy strategyModuleImpl; // plugins Rebalancer rebalancer; WithdrawalQueue withdrawalQueueImpl; @@ -50,7 +50,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { rewardsImpl = new Rewards(); hooksImpl = new Hooks(); feeModuleImpl = new Fee(); - allocationPointsModuleImpl = new AllocationPoints(); + strategyModuleImpl = new Strategy(); rebalancer = new Rebalancer(); withdrawalQueueImpl = new WithdrawalQueue(); @@ -61,7 +61,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), - allocationPointsModuleImpl: address(allocationPointsModuleImpl), + strategyModuleImpl: address(strategyModuleImpl), rebalancer: address(rebalancer) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); @@ -97,7 +97,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { vm.label(eulerAggregationVault.rewardsModule(), "rewardsModule"); vm.label(eulerAggregationVault.hooksModule(), "hooksModule"); vm.label(eulerAggregationVault.feeModule(), "feeModule"); - vm.label(eulerAggregationVault.allocationPointsModule(), "allocationPointsModule"); + vm.label(eulerAggregationVault.strategyModule(), "strategyModule"); vm.label(address(assetTST), "assetTST"); } diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 51d41383..9858a427 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -31,7 +31,7 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), - allocationPointsModuleImpl: address(allocationPointsModuleImpl), + strategyModuleImpl: address(strategyModuleImpl), rebalancer: address(rebalancer) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); diff --git a/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol index 61c23dba..7ffe98ee 100644 --- a/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol +++ b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol @@ -75,7 +75,7 @@ contract ToggleStrategyEmergencyStatusE2ETest is EulerAggregationVaultBase { // this to test a scneraio where a startegy `withdraw()` start reverting. // Guardian will set the strategy in emergency mode, harvest and withdraw should execute, // user will be able to withdraw from other strategy, losses will only be in the faulty strategy. - function testDepositRebalanceHarvestWIthdrawWithFaultyStartegy() public { + function testDepositRebalanceWithdrawWithFaultyStartegy() public { uint256 amountToDeposit = 10000e18; // deposit into aggregator @@ -98,7 +98,7 @@ contract ToggleStrategyEmergencyStatusE2ETest is EulerAggregationVaultBase { // rebalance into strategy // 2500 total points; 1000 for reserve(40%), 500(20%) for eTST, 1000(40%) for eTSTsecondary - // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary + // 10k deposited; 4k for reserve, 2k for eTST, 4k for eTSTsecondary vm.warp(block.timestamp + 86400); { IEulerAggregationVault.Strategy memory eTSTstrategyBefore = eulerAggregationVault.getStrategy(address(eTST)); @@ -143,37 +143,26 @@ contract ToggleStrategyEmergencyStatusE2ETest is EulerAggregationVaultBase { ); } - vm.warp(block.timestamp + 86400); - // mock an increase of aggregator balance due to yield - uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(eulerAggregationVault)); - uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); - uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; - uint256 eTSTYield = aggrNeweTSTUnderlyingBalance - aggrCurrenteTSTUnderlyingBalance; - - assetTST.mint(address(eTST), eTSTYield); - eTST.skim(type(uint256).max, address(eulerAggregationVault)); - - // set eTSTsecondary in emergency mode + // set eTST in emergency mode vm.prank(manager); - eulerAggregationVault.toggleStrategyEmergencyStatus(address(eTSTsecondary)); + eulerAggregationVault.toggleStrategyEmergencyStatus(address(eTST)); - // full withdraw, will have to withdraw from strategy as cash reserve is not enough + // full withdraw, will have to withdraw from strategy as cash reserve is not enough, user should be able to withdraw { uint256 amountToWithdraw = eulerAggregationVault.balanceOf(user1); - uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); - uint256 aggregatorTotalSupplyBefore = eulerAggregationVault.totalSupply(); uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + uint256 expectedAssets = eulerAggregationVault.convertToAssets(amountToWithdraw); vm.prank(user1); eulerAggregationVault.redeem(amountToWithdraw, user1, user1); - // assertEq(eTST.balanceOf(address(eulerAggregationVault)), 0); - // assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - amountToWithdraw); - // assertEq(eulerAggregationVault.totalSupply(), aggregatorTotalSupplyBefore - amountToWithdraw); - // assertEq( - // assetTST.balanceOf(user1), - // user1AssetTSTBalanceBefore + eulerAggregationVault.convertToAssets(amountToWithdraw) - // ); + assertTrue(eTST.balanceOf(address(eulerAggregationVault)) != 0); + assertEq(eulerAggregationVault.totalAssetsDeposited(), 0); + assertEq(eulerAggregationVault.totalSupply(), 0); + assertEq( + assetTST.balanceOf(user1), + user1AssetTSTBalanceBefore + expectedAssets + ); } } diff --git a/test/echidna/CryticERC4626TestsHarness.t.sol b/test/echidna/CryticERC4626TestsHarness.t.sol index 3eafa605..967452cd 100644 --- a/test/echidna/CryticERC4626TestsHarness.t.sol +++ b/test/echidna/CryticERC4626TestsHarness.t.sol @@ -11,7 +11,7 @@ import {Rewards} from "../../src/core/module/Rewards.sol"; import {Fee} from "../../src/core/module/Fee.sol"; import {EulerAggregationVaultFactory} from "../../src/core/EulerAggregationVaultFactory.sol"; import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; -import {AllocationPoints} from "../../src/core/module/AllocationPoints.sol"; +import {Strategy} from "../../src/core/module/Strategy.sol"; import {TestERC20Token} from "crytic-properties/ERC4626/util/TestERC20Token.sol"; contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { @@ -23,7 +23,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { Rewards rewardsImpl; Hooks hooksImpl; Fee feeModuleImpl; - AllocationPoints allocationPointsModuleImpl; + Strategy strategyModuleImpl; // plugins Rebalancer rebalancerPlugin; WithdrawalQueue withdrawalQueuePluginImpl; @@ -35,7 +35,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { rewardsImpl = new Rewards(); hooksImpl = new Hooks(); feeModuleImpl = new Fee(); - allocationPointsModuleImpl = new AllocationPoints(); + strategyModuleImpl = new Strategy(); rebalancerPlugin = new Rebalancer(); withdrawalQueuePluginImpl = new WithdrawalQueue(); @@ -46,7 +46,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), - allocationPointsModuleImpl: address(allocationPointsModuleImpl), + strategyModuleImpl: address(strategyModuleImpl), rebalancer: address(rebalancerPlugin) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); From 15c965fa7030e2d729f8a0ba6ee59c397a28bae4 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:58:47 +0300 Subject: [PATCH 257/316] chore: lint --- test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol index 7ffe98ee..88e80fb7 100644 --- a/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol +++ b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol @@ -159,10 +159,7 @@ contract ToggleStrategyEmergencyStatusE2ETest is EulerAggregationVaultBase { assertTrue(eTST.balanceOf(address(eulerAggregationVault)) != 0); assertEq(eulerAggregationVault.totalAssetsDeposited(), 0); assertEq(eulerAggregationVault.totalSupply(), 0); - assertEq( - assetTST.balanceOf(user1), - user1AssetTSTBalanceBefore + expectedAssets - ); + assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedAssets); } } From 6708cb06d2fb50af2e159c78d8f6dae37503881b Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 16 Jul 2024 18:08:07 +0300 Subject: [PATCH 258/316] set allocated to maxWithdraw() --- src/core/module/Strategy.sol | 4 +- ...ToggleStrategyEmergencyStatusE2ETest.t.sol | 133 ++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/src/core/module/Strategy.sol b/src/core/module/Strategy.sol index 3d490f87..b0efdca1 100644 --- a/src/core/module/Strategy.sol +++ b/src/core/module/Strategy.sol @@ -67,6 +67,8 @@ abstract contract StrategyModule is Shared { /// @dev This should be used as a cricuit-breaker to exclude a faulty strategy from being harvest or rebalanced. /// It also deduct all the deposited amounts into the strategy as loss, and uses a loss socialization mechanism. /// This is needed, in case the aggregation vault can no longer withdraw from a certain strategy. + /// In the case of switching a strategy from Emergency to Active again, the max withdrawable amount from the strategy + /// will be set as the allocated amount, and will be set as interest during the next time gulp() is called. function toggleStrategyEmergencyStatus(address _strategy) external virtual nonReentrant { if (_strategy == address(0)) revert Errors.CanNotToggleStrategyEmergencyStatus(); @@ -86,7 +88,7 @@ abstract contract StrategyModule is Shared { $.strategies[_strategy].status = IEulerAggregationVault.StrategyStatus.Active; $.totalAllocationPoints += strategyCached.allocationPoints; - $.totalAllocated += strategyCached.allocated; + $.totalAllocated += IERC4626(_strategy).maxWithdraw(address(this)); } } diff --git a/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol index 88e80fb7..b6ff0a88 100644 --- a/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol +++ b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol @@ -15,6 +15,7 @@ import { contract ToggleStrategyEmergencyStatusE2ETest is EulerAggregationVaultBase { uint256 user1InitialBalance = 100000e18; + uint256 user2InitialBalance = 100000e18; IEVault eTSTsecondary; @@ -38,6 +39,7 @@ contract ToggleStrategyEmergencyStatusE2ETest is EulerAggregationVaultBase { } assetTST.mint(user1, user1InitialBalance); + assetTST.mint(user2, user2InitialBalance); } function testToggleStrategyEmergencyStatus() public { @@ -178,4 +180,135 @@ contract ToggleStrategyEmergencyStatusE2ETest is EulerAggregationVaultBase { vm.expectRevert(ErrorsLib.CanNotRemoveStrategyInEmergencyStatus.selector); eulerAggregationVault.removeStrategy(address(eTSTsecondary)); } + + function testSetEmergencyAndReactive() public { + uint256 amountToDeposit = 10000e18; + + // deposit into aggregator + { + vm.startPrank(user1); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user1); + vm.stopPrank(); + + vm.startPrank(user2); + assetTST.approve(address(eulerAggregationVault), amountToDeposit); + eulerAggregationVault.deposit(amountToDeposit, user2); + vm.stopPrank(); + } + + // rebalance into strategy + // 2500 total points; 1000 for reserve(40%), 500(20%) for eTST, 1000(40%) for eTSTsecondary + // 10k deposited; 4k for reserve, 2k for eTST, 4k for eTSTsecondary + vm.warp(block.timestamp + 86400); + { + IEulerAggregationVault.Strategy memory eTSTstrategyBefore = eulerAggregationVault.getStrategy(address(eTST)); + IEulerAggregationVault.Strategy memory eTSTsecondarystrategyBefore = + eulerAggregationVault.getStrategy(address(eTSTsecondary)); + + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), eTSTstrategyBefore.allocated); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), + eTSTsecondarystrategyBefore.allocated + ); + + uint256 expectedeTSTStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * eTSTstrategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); + uint256 expectedeTSTsecondaryStrategyCash = eulerAggregationVault.totalAssetsAllocatable() + * eTSTsecondarystrategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); + + assertTrue(expectedeTSTStrategyCash != 0); + assertTrue(expectedeTSTsecondaryStrategyCash != 0); + + address[] memory strategiesToRebalance = new address[](2); + strategiesToRebalance[0] = address(eTST); + strategiesToRebalance[1] = address(eTSTsecondary); + vm.prank(user1); + rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + + assertEq( + eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash + ); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedeTSTStrategyCash); + assertEq( + eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), + expectedeTSTsecondaryStrategyCash + ); + assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); + assertEq( + (eulerAggregationVault.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash + ); + assertEq( + assetTST.balanceOf(address(eulerAggregationVault)), + amountToDeposit * 2 - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) + ); + } + + // set eTST in emergency mode + uint256 totalAllocationBefore = eulerAggregationVault.totalAllocationPoints(); + vm.prank(manager); + eulerAggregationVault.toggleStrategyEmergencyStatus(address(eTST)); + assertEq( + eulerAggregationVault.totalAllocationPoints(), + totalAllocationBefore - (eulerAggregationVault.getStrategy(address(eTST))).allocationPoints + ); + + // user 1 full withdraw, will have to withdraw from strategy as cash reserve is not enough, user should be able to withdraw + { + uint256 amountToWithdraw = eulerAggregationVault.balanceOf(user1); + uint256 user1AssetTSTBalanceBefore = assetTST.balanceOf(user1); + uint256 user2AssetTSTBalanceBefore = assetTST.balanceOf(user2); + uint256 expectedAssets = eulerAggregationVault.convertToAssets(amountToWithdraw); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + + assertTrue(totalAssetsDepositedBefore != 0); + + vm.prank(user1); + eulerAggregationVault.redeem(amountToWithdraw, user1, user1); + + assertTrue(eTST.balanceOf(address(eulerAggregationVault)) != 0); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - expectedAssets); + assertEq(eulerAggregationVault.totalSupply(), eulerAggregationVault.balanceOf(user2)); + assertEq(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedAssets); + assertEq(assetTST.balanceOf(user2), user2AssetTSTBalanceBefore); + } + + vm.warp(block.timestamp + 86400); + + // re-activate eTST back again + totalAllocationBefore = eulerAggregationVault.totalAllocationPoints(); + vm.prank(manager); + eulerAggregationVault.toggleStrategyEmergencyStatus(address(eTST)); + assertEq( + eulerAggregationVault.totalAllocationPoints(), + totalAllocationBefore + (eulerAggregationVault.getStrategy(address(eTST))).allocationPoints + ); + + eulerAggregationVault.gulp(); + + // user 2 full withdraw + { + uint256 amountToWithdraw = eulerAggregationVault.balanceOf(user2); + uint256 user2AssetTSTBalanceBefore = assetTST.balanceOf(user2); + uint256 expectedAssets = eulerAggregationVault.convertToAssets(amountToWithdraw); + uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); + + assertTrue(totalAssetsDepositedBefore != 0); + + vm.prank(user2); + eulerAggregationVault.redeem(amountToWithdraw, user2, user2); + + assertEq(eTST.balanceOf(address(eulerAggregationVault)), 0); + assertEq(eulerAggregationVault.totalAssetsDeposited(), 0); + assertEq(eulerAggregationVault.totalSupply(), 0); + assertEq(assetTST.balanceOf(user2), user2AssetTSTBalanceBefore + expectedAssets); + } + + EulerAggregationVault.Strategy memory eTSTsecondaryStrategy = + eulerAggregationVault.getStrategy(address(eTSTsecondary)); + EulerAggregationVault.AggregationVaultSavingRate memory avsr = + eulerAggregationVault.getAggregationVaultSavingRate(); + + assertEq(avsr.interestLeft, eTSTsecondaryStrategy.allocated); + } } From f7cc381efd7c74febce885a7987dd13c99da685b Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 17 Jul 2024 13:02:29 +0300 Subject: [PATCH 259/316] fix fuzz tests --- src/core/EulerAggregationVault.sol | 18 ++++++++++++++---- .../fuzz/DepositWithdrawMintBurnFuzzTest.t.sol | 14 ++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 44cb2474..3c085fea 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -10,9 +10,10 @@ import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; // contracts import {Dispatch} from "./Dispatch.sol"; import { - ERC4626Upgradeable, - ERC20Upgradeable + ERC20Upgradeable, + ERC4626Upgradeable } from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import {ERC20VotesUpgradeable} from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {Shared} from "./common/Shared.sol"; @@ -33,6 +34,7 @@ import {EventsLib as Events} from "./lib/EventsLib.sol"; /// @dev inspired by Yearn v3 ❤️ contract EulerAggregationVault is ERC4626Upgradeable, + ERC20VotesUpgradeable, AccessControlEnumerableUpgradeable, Dispatch, IEulerAggregationVault @@ -427,6 +429,11 @@ contract EulerAggregationVault is return super.redeem(_shares, _receiver, _owner); } + /// @dev See {IERC20Metadata-decimals}. + function decimals() public view virtual override (ERC4626Upgradeable, ERC20Upgradeable) returns (uint8) { + return ERC4626Upgradeable.decimals(); + } + /// @notice Return the total amount of assets deposited, plus the accrued interest. /// @return uint256 total amount function totalAssets() public view override returns (uint256) { @@ -596,8 +603,11 @@ contract EulerAggregationVault is /// @dev Calling .balanceTrackerHook() passing the address total balance /// @param from Address sending the amount /// @param to Address receiving the amount - function _update(address from, address to, uint256 value) internal override { - super._update(from, to, value); + function _update(address from, address to, uint256 value) + internal + override (ERC20VotesUpgradeable, ERC20Upgradeable) + { + ERC20VotesUpgradeable._update(from, to, value); if (from == to) return; diff --git a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol index 56e49e93..83eeb52a 100644 --- a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol +++ b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol @@ -4,13 +4,15 @@ pragma solidity ^0.8.0; import {console2, EulerAggregationVaultBase, EulerAggregationVault} from "../common/EulerAggregationVaultBase.t.sol"; contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { - uint256 constant MAX_ALLOWED = type(uint256).max; + uint256 constant MAX_ALLOWED = type(uint208).max; function setUp() public virtual override { super.setUp(); } function testFuzzDeposit(uint256 _assets) public { + _assets = bound(_assets, 0, MAX_ALLOWED); + // moch the scenario of _assets ownership assetTST.mint(user1, _assets); @@ -35,7 +37,7 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { ) public { vm.assume(_receiver != address(0)); - _assetsToDeposit = bound(_assetsToDeposit, 1, type(uint256).max - 1); + _assetsToDeposit = bound(_assetsToDeposit, 1, MAX_ALLOWED - 1); _assetsToWithdraw = bound(_assetsToWithdraw, 0, _assetsToDeposit); _timestampAfterDeposit = bound(_timestampAfterDeposit, 0, 86400); @@ -61,9 +63,11 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { } function testFuzzMint(uint256 _shares) public { - // moch the scenario of _assets ownership + // mock the scenario of _assets ownership uint256 assets = eulerAggregationVault.previewMint(_shares); + if (assets > MAX_ALLOWED) assets = MAX_ALLOWED; assetTST.mint(user1, assets); + _shares = eulerAggregationVault.previewDeposit(assets); uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); uint256 totalSupplyBefore = eulerAggregationVault.totalSupply(); @@ -87,12 +91,14 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { vm.assume(_receiver != address(0)); _sharesToMint = bound(_sharesToMint, 1, type(uint256).max - 1); - _sharesToRedeem = bound(_sharesToRedeem, 0, _sharesToMint); _timestampAfterDeposit = bound(_timestampAfterDeposit, 0, 86400); // deposit uint256 assetsToDeposit = eulerAggregationVault.previewMint(_sharesToMint); + if (assetsToDeposit > MAX_ALLOWED) assetsToDeposit = MAX_ALLOWED; assetTST.mint(user1, assetsToDeposit); + _sharesToMint = eulerAggregationVault.previewDeposit(assetsToDeposit); + _sharesToRedeem = bound(_sharesToRedeem, 0, _sharesToMint); _mint(user1, assetsToDeposit, _sharesToMint); vm.warp(block.timestamp + _timestampAfterDeposit); From 5a30dbb7be313e26352455a32c73d6b705f7eca5 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 18 Jul 2024 00:09:43 +0300 Subject: [PATCH 260/316] fix role setup --- src/core/EulerAggregationVault.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 3c085fea..3e813f98 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -91,7 +91,7 @@ contract EulerAggregationVault is _grantRole(DEFAULT_ADMIN_ROLE, _initParams.aggregationVaultOwner); // Setup role admins - _setRoleAdmin(STRATEGY_OPERATOR, STRATEGY_OPERATOR_ADMIN); + _setRoleAdmin(GUARDIAN, GUARDIAN_ADMIN); _setRoleAdmin(STRATEGY_OPERATOR, STRATEGY_OPERATOR_ADMIN); _setRoleAdmin(AGGREGATION_VAULT_MANAGER, AGGREGATION_VAULT_MANAGER_ADMIN); From 18bb998674b13c11f72142f587873b1876064190 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 18 Jul 2024 00:10:03 +0300 Subject: [PATCH 261/316] remove --sizes from CI --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 49b34ed5..c633cfdb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: version: nightly - name: Run foundry build - run: forge build --force --sizes --skip test + run: forge build --force --skip test - name: Run foundry fmt check run: forge fmt --check From efa5eda20e9382f6d99fcd8bf7eada43742801f9 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 18 Jul 2024 10:33:34 +0300 Subject: [PATCH 262/316] test: voting power invariant --- test/invariant/EulerAggregationLayerInvariants.t.sol | 8 ++++++++ test/invariant/util/Actor.sol | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 7e691ead..2b312a9e 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -174,6 +174,14 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { assertEq(eulerAggregationVault.getStrategy(address(0)).cap, 0); } + function invariant_votingPower() public view { + address[] memory actorsList = actorUtil.getActors(); + + for (uint256 i; i < actorsList.length; i++) { + assertEq(eulerAggregationVault.balanceOf(actorsList[i]), eulerAggregationVault.getVotes(actorsList[i])); + } + } + function _deployOtherStrategies() private { eTSTsecond = IEVault( factory.createProxy(address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount)) diff --git a/test/invariant/util/Actor.sol b/test/invariant/util/Actor.sol index fe858335..9f36e4f2 100644 --- a/test/invariant/util/Actor.sol +++ b/test/invariant/util/Actor.sol @@ -42,6 +42,16 @@ contract Actor is Test { return (randomActor, randomActorIndex); } + function getActors() external view returns (address[] memory) { + address[] memory actorsList = new address[](actors.length); + + for (uint256 i; i < actors.length; i++) { + actorsList[i] = actors[i]; + } + + return actorsList; + } + function _getActor(uint256 _actorIndexSeed) internal view returns (address) { return actors[bound(_actorIndexSeed, 0, actors.length - 1)]; } From fc2fcadc706475959addca522ef9f7b5bfc4a383 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 18 Jul 2024 11:27:34 +0300 Subject: [PATCH 263/316] Rebalance module --- src/core/Dispatch.sol | 12 ++++- src/core/EulerAggregationVault.sol | 38 +++----------- src/core/EulerAggregationVaultFactory.sol | 6 ++- src/core/interface/IEulerAggregationVault.sol | 1 + src/core/module/Rebalance.sol | 52 +++++++++++++++++++ test/A16zPropertyTests.t.sol | 4 ++ test/common/EulerAggregationVaultBase.t.sol | 4 ++ test/e2e/BalanceForwarderE2ETest.t.sol | 1 + test/echidna/CryticERC4626TestsHarness.t.sol | 4 ++ 9 files changed, 90 insertions(+), 32 deletions(-) create mode 100644 src/core/module/Rebalance.sol diff --git a/src/core/Dispatch.sol b/src/core/Dispatch.sol index ec0518af..1786852c 100644 --- a/src/core/Dispatch.sol +++ b/src/core/Dispatch.sol @@ -7,6 +7,7 @@ import {HooksModule} from "./module/Hooks.sol"; import {RewardsModule} from "./module/Rewards.sol"; import {StrategyModule} from "./module/Strategy.sol"; import {FeeModule} from "./module/Fee.sol"; +import {RebalanceModule} from "./module/Rebalance.sol"; /// @title Dispatch contract /// @custom:security-contact security@euler.xyz @@ -18,17 +19,26 @@ abstract contract Dispatch is RewardsModule, HooksModule, FeeModule, StrategyMod address public immutable hooksModule; address public immutable feeModule; address public immutable strategyModule; + address public immutable rebalanceModule; /// @dev Constructor. /// @param _rewardsModule Address of Rewards module. /// @param _hooksModule Address of Hooks module. /// @param _feeModule Address of Fee module. /// @param _strategyModule Address of Strategy module. - constructor(address _rewardsModule, address _hooksModule, address _feeModule, address _strategyModule) { + /// @param _rebalanceModule Address of Rebalance module. + constructor( + address _rewardsModule, + address _hooksModule, + address _feeModule, + address _strategyModule, + address _rebalanceModule + ) { rewardsModule = _rewardsModule; hooksModule = _hooksModule; feeModule = _feeModule; strategyModule = _strategyModule; + rebalanceModule = _rebalanceModule; } // Modifier proxies the function call to a module and low-level returns the result diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 3e813f98..7f049fa8 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -61,7 +61,8 @@ contract EulerAggregationVault is _constructorParams.rewardsModule, _constructorParams.hooksModule, _constructorParams.feeModule, - _constructorParams.strategyModule + _constructorParams.strategyModule, + _constructorParams.rebalanceModule ) {} @@ -193,6 +194,12 @@ contract EulerAggregationVault is /// @dev See {RewardsModule-disableBalanceForwarder}. function disableBalanceForwarder() external override use(rewardsModule) {} + function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) + external + override + use(rewardsModule) + {} + /// @notice Set a new address for Rebalancer plugin. /// @dev Can only be called by an address with the `AGGREGATION_VAULT_MANAGER` role. /// @param _rebalancer New Rebalancer contract address. @@ -206,35 +213,6 @@ contract EulerAggregationVault is $.rebalancer = _rebalancer; } - /// @notice Rebalance strategy by depositing or withdrawing the amount to rebalance to hit target allocation. - /// @dev Can only be called only by the WithdrawalQueue plugin. - /// @param _strategy Strategy address. - /// @param _amountToRebalance Amount to deposit or withdraw. - /// @param _isDeposit bool to indicate if it is a deposit or a withdraw. - function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external nonReentrant { - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - - if (_msgSender() != $.rebalancer) revert Errors.NotRebalancer(); - - IEulerAggregationVault.Strategy memory strategyData = $.strategies[_strategy]; - - if (strategyData.status != IEulerAggregationVault.StrategyStatus.Active) return; - - if (_isDeposit) { - // Do required approval (safely) and deposit - IERC20(asset()).safeIncreaseAllowance(_strategy, _amountToRebalance); - IERC4626(_strategy).deposit(_amountToRebalance, address(this)); - $.strategies[_strategy].allocated = (strategyData.allocated + _amountToRebalance).toUint120(); - $.totalAllocated += _amountToRebalance; - } else { - IERC4626(_strategy).withdraw(_amountToRebalance, address(this), address(this)); - $.strategies[_strategy].allocated = (strategyData.allocated - _amountToRebalance).toUint120(); - $.totalAllocated -= _amountToRebalance; - } - - emit Events.Rebalance(_strategy, _amountToRebalance, _isDeposit); - } - /// @notice Harvest all the strategies. /// @dev This function will loop through the strategies following the withdrawal queue order and harvest all. /// @dev Harvest yield and losses will be aggregated and only net yield/loss will be accounted. diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index 7506fcce..54f7257e 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -22,6 +22,7 @@ contract EulerAggregationVaultFactory is Ownable { address public immutable hooksModuleImpl; address public immutable feeModuleImpl; address public immutable strategyModuleImpl; + address public immutable rebalanceModuleImpl; /// plugins /// @dev Rebalancer plugin contract address, one instance can serve different aggregation vaults address public immutable rebalancer; @@ -40,6 +41,7 @@ contract EulerAggregationVaultFactory is Ownable { address hooksModuleImpl; address feeModuleImpl; address strategyModuleImpl; + address rebalanceModuleImpl; address rebalancer; } @@ -56,6 +58,7 @@ contract EulerAggregationVaultFactory is Ownable { hooksModuleImpl = _factoryParams.hooksModuleImpl; feeModuleImpl = _factoryParams.feeModuleImpl; strategyModuleImpl = _factoryParams.strategyModuleImpl; + rebalanceModuleImpl = _factoryParams.rebalanceModuleImpl; rebalancer = _factoryParams.rebalancer; } @@ -96,7 +99,8 @@ contract EulerAggregationVaultFactory is Ownable { rewardsModule: Clones.clone(rewardsModuleImpl), hooksModule: Clones.clone(hooksModuleImpl), feeModule: Clones.clone(feeModuleImpl), - strategyModule: Clones.clone(strategyModuleImpl) + strategyModule: Clones.clone(strategyModuleImpl), + rebalanceModule: Clones.clone(rebalanceModuleImpl) }); EulerAggregationVault eulerAggregationVault = new EulerAggregationVault(aggregationVaultConstructorParams); diff --git a/src/core/interface/IEulerAggregationVault.sol b/src/core/interface/IEulerAggregationVault.sol index 3440d430..567f5723 100644 --- a/src/core/interface/IEulerAggregationVault.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -8,6 +8,7 @@ interface IEulerAggregationVault { address hooksModule; address feeModule; address strategyModule; + address rebalanceModule; } /// @dev Struct to pass init() call params. diff --git a/src/core/module/Rebalance.sol b/src/core/module/Rebalance.sol new file mode 100644 index 00000000..a7e07e87 --- /dev/null +++ b/src/core/module/Rebalance.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +// interfaces +import {IEulerAggregationVault} from "../interface/IEulerAggregationVault.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; +// contracts +import {Shared} from "../common/Shared.sol"; +import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgradeable.sol"; +// libs +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; +import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; +import {EventsLib as Events} from "../lib/EventsLib.sol"; + +abstract contract RebalanceModule is ContextUpgradeable, Shared { + using SafeERC20 for IERC20; + using SafeCast for uint256; + + /// @notice Rebalance strategy by depositing or withdrawing the amount to rebalance to hit target allocation. + /// @dev Can only be called only by the WithdrawalQueue plugin. + /// @param _strategy Strategy address. + /// @param _amountToRebalance Amount to deposit or withdraw. + /// @param _isDeposit bool to indicate if it is a deposit or a withdraw. + function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external virtual nonReentrant { + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + + if (_msgSender() != $.rebalancer) revert Errors.NotRebalancer(); + + IEulerAggregationVault.Strategy memory strategyData = $.strategies[_strategy]; + + if (strategyData.status != IEulerAggregationVault.StrategyStatus.Active) return; + + if (_isDeposit) { + // Do required approval (safely) and deposit + IERC20(IERC4626(address(this)).asset()).safeIncreaseAllowance(_strategy, _amountToRebalance); + IERC4626(_strategy).deposit(_amountToRebalance, address(this)); + $.strategies[_strategy].allocated = (strategyData.allocated + _amountToRebalance).toUint120(); + $.totalAllocated += _amountToRebalance; + } else { + IERC4626(_strategy).withdraw(_amountToRebalance, address(this), address(this)); + $.strategies[_strategy].allocated = (strategyData.allocated - _amountToRebalance).toUint120(); + $.totalAllocated -= _amountToRebalance; + } + + emit Events.Rebalance(_strategy, _amountToRebalance, _isDeposit); + } +} + +contract Rebalance is RebalanceModule {} diff --git a/test/A16zPropertyTests.t.sol b/test/A16zPropertyTests.t.sol index cd243e3d..d28e87db 100644 --- a/test/A16zPropertyTests.t.sol +++ b/test/A16zPropertyTests.t.sol @@ -9,6 +9,7 @@ import {Rebalancer} from "../src/plugin/Rebalancer.sol"; import {Hooks} from "../src/core/module/Hooks.sol"; import {Rewards} from "../src/core/module/Rewards.sol"; import {Fee} from "../src/core/module/Fee.sol"; +import {Rebalance} from "../src/core/module/Rebalance.sol"; import {EulerAggregationVaultFactory} from "../src/core/EulerAggregationVaultFactory.sol"; import {WithdrawalQueue} from "../src/plugin/WithdrawalQueue.sol"; import {Strategy} from "../src/core/module/Strategy.sol"; @@ -25,6 +26,7 @@ contract A16zPropertyTests is ERC4626Test { Hooks hooksImpl; Fee feeModuleImpl; Strategy strategyModuleImpl; + Rebalance rebalanceModuleImpl; // plugins Rebalancer rebalancerPlugin; WithdrawalQueue withdrawalQueuePluginImpl; @@ -39,6 +41,7 @@ contract A16zPropertyTests is ERC4626Test { hooksImpl = new Hooks(); feeModuleImpl = new Fee(); strategyModuleImpl = new Strategy(); + rebalanceModuleImpl = new Rebalance(); rebalancerPlugin = new Rebalancer(); withdrawalQueuePluginImpl = new WithdrawalQueue(); @@ -50,6 +53,7 @@ contract A16zPropertyTests is ERC4626Test { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), strategyModuleImpl: address(strategyModuleImpl), + rebalanceModuleImpl: address(rebalanceModuleImpl), rebalancer: address(rebalancerPlugin) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index ed99ec1f..42e182aa 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -11,6 +11,7 @@ import {Rebalancer} from "../../src/plugin/Rebalancer.sol"; import {Hooks, HooksModule} from "../../src/core/module/Hooks.sol"; import {Rewards} from "../../src/core/module/Rewards.sol"; import {Fee} from "../../src/core/module/Fee.sol"; +import {Rebalance} from "../../src/core/module/Rebalance.sol"; import {EulerAggregationVaultFactory} from "../../src/core/EulerAggregationVaultFactory.sol"; import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; import {Strategy} from "../../src/core/module/Strategy.sol"; @@ -30,6 +31,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { Hooks hooksImpl; Fee feeModuleImpl; Strategy strategyModuleImpl; + Rebalance rebalanceModuleImpl; // plugins Rebalancer rebalancer; WithdrawalQueue withdrawalQueueImpl; @@ -51,6 +53,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { hooksImpl = new Hooks(); feeModuleImpl = new Fee(); strategyModuleImpl = new Strategy(); + rebalanceModuleImpl = new Rebalance(); rebalancer = new Rebalancer(); withdrawalQueueImpl = new WithdrawalQueue(); @@ -62,6 +65,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), strategyModuleImpl: address(strategyModuleImpl), + rebalanceModuleImpl: address(rebalanceModuleImpl), rebalancer: address(rebalancer) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 9858a427..6fd9b8e4 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -32,6 +32,7 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), strategyModuleImpl: address(strategyModuleImpl), + rebalanceModuleImpl: address(rebalanceModuleImpl), rebalancer: address(rebalancer) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); diff --git a/test/echidna/CryticERC4626TestsHarness.t.sol b/test/echidna/CryticERC4626TestsHarness.t.sol index 967452cd..e5a708b9 100644 --- a/test/echidna/CryticERC4626TestsHarness.t.sol +++ b/test/echidna/CryticERC4626TestsHarness.t.sol @@ -9,6 +9,7 @@ import {Rebalancer} from "../../src/plugin/Rebalancer.sol"; import {Hooks} from "../../src/core/module/Hooks.sol"; import {Rewards} from "../../src/core/module/Rewards.sol"; import {Fee} from "../../src/core/module/Fee.sol"; +import {Rebalance} from "../../src/core/module/Rebalance.sol"; import {EulerAggregationVaultFactory} from "../../src/core/EulerAggregationVaultFactory.sol"; import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; import {Strategy} from "../../src/core/module/Strategy.sol"; @@ -24,6 +25,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { Hooks hooksImpl; Fee feeModuleImpl; Strategy strategyModuleImpl; + Rebalance rebalanceModuleImpl; // plugins Rebalancer rebalancerPlugin; WithdrawalQueue withdrawalQueuePluginImpl; @@ -36,6 +38,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { hooksImpl = new Hooks(); feeModuleImpl = new Fee(); strategyModuleImpl = new Strategy(); + rebalanceModuleImpl = new Rebalance(); rebalancerPlugin = new Rebalancer(); withdrawalQueuePluginImpl = new WithdrawalQueue(); @@ -47,6 +50,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), strategyModuleImpl: address(strategyModuleImpl), + rebalanceModuleImpl: address(rebalanceModuleImpl), rebalancer: address(rebalancerPlugin) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); From ba9014874cc8a174148c9a565679501b7ae09c4b Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 19 Jul 2024 15:24:48 +0300 Subject: [PATCH 264/316] refactor: remove Rebalancer plugin and add Rebalance module --- src/core/Dispatch.sol | 2 +- src/core/EulerAggregationVault.sol | 28 +----- src/core/EulerAggregationVaultFactory.sol | 6 -- src/core/interface/IEulerAggregationVault.sol | 2 - src/core/lib/StorageLib.sol | 2 - src/core/module/Rebalance.sol | 89 ++++++++++++++--- src/plugin/Rebalancer.sol | 97 ------------------- test/A16zPropertyTests.t.sol | 6 +- test/common/EulerAggregationVaultBase.t.sol | 6 +- test/e2e/BalanceForwarderE2ETest.t.sol | 3 +- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 12 +-- test/e2e/HarvestRedeemE2ETest.t.sol | 2 +- test/e2e/PerformanceFeeE2ETest.t.sol | 2 +- test/e2e/StrategyCapE2ETest.t.sol | 6 +- ...ToggleStrategyEmergencyStatusE2ETest.t.sol | 4 +- test/echidna/CryticERC4626TestsHarness.t.sol | 6 +- .../EulerAggregationLayerInvariants.t.sol | 3 +- test/invariant/handler/RebalancerHandler.sol | 8 +- .../handler/WithdrawalQueueHandler.sol | 1 - test/unit/GulpTest.t.sol | 2 +- test/unit/HarvestTest.t.sol | 2 +- test/unit/RebalanceTest.t.sol | 25 ++--- test/unit/RemoveStrategy.t.sol | 2 +- test/unit/SetRebalancerTest.t.sol | 25 ----- 24 files changed, 108 insertions(+), 233 deletions(-) delete mode 100644 src/plugin/Rebalancer.sol delete mode 100644 test/unit/SetRebalancerTest.t.sol diff --git a/src/core/Dispatch.sol b/src/core/Dispatch.sol index 1786852c..f1cec2dd 100644 --- a/src/core/Dispatch.sol +++ b/src/core/Dispatch.sol @@ -14,7 +14,7 @@ import {RebalanceModule} from "./module/Rebalance.sol"; /// @author Euler Labs (https://www.eulerlabs.com/) /// @dev This contract implement the modifier to use for forwarding calls to a specific module using delegateCall. /// @dev Copied from https://github.com/euler-xyz/euler-vault-kit/blob/55d1a1fd7d572372f1c8b9f58aba0604bda3ca4f/src/EVault/Dispatch.sol. -abstract contract Dispatch is RewardsModule, HooksModule, FeeModule, StrategyModule { +abstract contract Dispatch is RewardsModule, HooksModule, FeeModule, StrategyModule, RebalanceModule { address public immutable rewardsModule; address public immutable hooksModule; address public immutable feeModule; diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 7f049fa8..0f489cf1 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -78,7 +78,6 @@ contract EulerAggregationVault is AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); $.locked = REENTRANCYLOCK__UNLOCKED; $.withdrawalQueue = _initParams.withdrawalQueuePlugin; - $.rebalancer = _initParams.rebalancerPlugin; $.balanceTracker = _initParams.balanceTracker; $.strategies[address(0)] = IEulerAggregationVault.Strategy({ allocated: 0, @@ -194,24 +193,7 @@ contract EulerAggregationVault is /// @dev See {RewardsModule-disableBalanceForwarder}. function disableBalanceForwarder() external override use(rewardsModule) {} - function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) - external - override - use(rewardsModule) - {} - - /// @notice Set a new address for Rebalancer plugin. - /// @dev Can only be called by an address with the `AGGREGATION_VAULT_MANAGER` role. - /// @param _rebalancer New Rebalancer contract address. - function setRebalancer(address _rebalancer) external onlyRole(AGGREGATION_VAULT_MANAGER) { - if (_rebalancer == address(0)) revert Errors.InvalidRebalancerPlugin(); - - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - - emit Events.SetRebalancer($.rebalancer, _rebalancer); - - $.rebalancer = _rebalancer; - } + function executeRebalance(address[] calldata _strategies) external override use(rebalanceModule) {} /// @notice Harvest all the strategies. /// @dev This function will loop through the strategies following the withdrawal queue order and harvest all. @@ -338,14 +320,6 @@ contract EulerAggregationVault is return $.withdrawalQueue; } - /// @notice Get the Rebalancer plugin address. - /// @return address Rebalancer address. - function rebalancer() external view returns (address) { - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - - return $.rebalancer; - } - /// @notice Get the performance fee config. /// @return adddress Fee recipient. /// @return uint256 Fee percentage. diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index 54f7257e..5a363c83 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -23,9 +23,6 @@ contract EulerAggregationVaultFactory is Ownable { address public immutable feeModuleImpl; address public immutable strategyModuleImpl; address public immutable rebalanceModuleImpl; - /// plugins - /// @dev Rebalancer plugin contract address, one instance can serve different aggregation vaults - address public immutable rebalancer; /// @dev Mapping to set whitelisted WhithdrawalQueue plugin implementation. mapping(address => bool) public isWhitelistedWithdrawalQueueImpl; /// @dev An array of the whitelisted WithdrawalQueue plugin implementations. @@ -42,7 +39,6 @@ contract EulerAggregationVaultFactory is Ownable { address feeModuleImpl; address strategyModuleImpl; address rebalanceModuleImpl; - address rebalancer; } event WhitelistWithdrawalQueueImpl(address withdrawalQueueImpl); @@ -59,7 +55,6 @@ contract EulerAggregationVaultFactory is Ownable { feeModuleImpl = _factoryParams.feeModuleImpl; strategyModuleImpl = _factoryParams.strategyModuleImpl; rebalanceModuleImpl = _factoryParams.rebalanceModuleImpl; - rebalancer = _factoryParams.rebalancer; } /// @notice Whitelist a new WithdrawalQueue implementation. @@ -108,7 +103,6 @@ contract EulerAggregationVaultFactory is Ownable { aggregationVaultOwner: msg.sender, asset: _asset, withdrawalQueuePlugin: Clones.clone(_withdrawalQueueImpl), - rebalancerPlugin: rebalancer, balanceTracker: balanceTracker, name: _name, symbol: _symbol, diff --git a/src/core/interface/IEulerAggregationVault.sol b/src/core/interface/IEulerAggregationVault.sol index 567f5723..00d9ab8c 100644 --- a/src/core/interface/IEulerAggregationVault.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -16,7 +16,6 @@ interface IEulerAggregationVault { address aggregationVaultOwner; address asset; address withdrawalQueuePlugin; - address rebalancerPlugin; address balanceTracker; string name; string symbol; @@ -60,7 +59,6 @@ interface IEulerAggregationVault { Emergency } - function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external; function gulp() external; function harvest() external; function executeStrategyWithdraw(address _strategy, uint256 _withdrawAmount) external returns (uint256); diff --git a/src/core/lib/StorageLib.sol b/src/core/lib/StorageLib.sol index 19f8ed24..9e34d645 100644 --- a/src/core/lib/StorageLib.sol +++ b/src/core/lib/StorageLib.sol @@ -18,8 +18,6 @@ struct AggregationVaultStorage { address feeRecipient; /// WithdrawalQueue plugin address address withdrawalQueue; - /// Rebalancer plugin address - address rebalancer; /// Mapping between strategy address and it's allocation config mapping(address => IEulerAggregationVault.Strategy) strategies; /// lastInterestUpdate: last timestamo where interest was updated. diff --git a/src/core/module/Rebalance.sol b/src/core/module/Rebalance.sol index a7e07e87..b174ea04 100644 --- a/src/core/module/Rebalance.sol +++ b/src/core/module/Rebalance.sol @@ -19,33 +19,92 @@ abstract contract RebalanceModule is ContextUpgradeable, Shared { using SafeERC20 for IERC20; using SafeCast for uint256; + /// @notice Rebalance strategies allocation for a specific curated vault. + /// @param _strategies Strategies addresses. + function executeRebalance(address[] calldata _strategies) external virtual nonReentrant { + // IEulerAggregationVault(_aggregationVault).gulp(); + + for (uint256 i; i < _strategies.length; ++i) { + _rebalance(_strategies[i]); + } + } + /// @notice Rebalance strategy by depositing or withdrawing the amount to rebalance to hit target allocation. - /// @dev Can only be called only by the WithdrawalQueue plugin. + /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. + /// If current allocation is less than target allocation, the aggregator will: + /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash + /// - If all the available cash is greater than the max deposit, deposit the max deposit /// @param _strategy Strategy address. - /// @param _amountToRebalance Amount to deposit or withdraw. - /// @param _isDeposit bool to indicate if it is a deposit or a withdraw. - function rebalance(address _strategy, uint256 _amountToRebalance, bool _isDeposit) external virtual nonReentrant { - AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); + function _rebalance(address _strategy) private { + if (_strategy == address(0)) { + return; //nothing to rebalance as that's the cash reserve + } - if (_msgSender() != $.rebalancer) revert Errors.NotRebalancer(); + AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); IEulerAggregationVault.Strategy memory strategyData = $.strategies[_strategy]; if (strategyData.status != IEulerAggregationVault.StrategyStatus.Active) return; - if (_isDeposit) { + uint256 totalAllocationPointsCache = $.totalAllocationPoints; + uint256 totalAssetsAllocatableCache = IEulerAggregationVault(address(this)).totalAssetsAllocatable(); + uint256 targetAllocation = + totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; + + if ((strategyData.cap > 0) && (targetAllocation > strategyData.cap)) targetAllocation = strategyData.cap; + + uint256 amountToRebalance; + bool isDeposit; + if (strategyData.allocated > targetAllocation) { + // Withdraw + amountToRebalance = strategyData.allocated - targetAllocation; + + uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(address(this)); + if (amountToRebalance > maxWithdraw) { + amountToRebalance = maxWithdraw; + } + + isDeposit = false; + } else if (strategyData.allocated < targetAllocation) { + // Deposit + uint256 targetCash = totalAssetsAllocatableCache + * IEulerAggregationVault(address(this)).getStrategy(address(0)).allocationPoints + / totalAllocationPointsCache; + uint256 currentCash = totalAssetsAllocatableCache - IEulerAggregationVault(address(this)).totalAllocated(); + + // Calculate available cash to put in strategies + uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; + + amountToRebalance = targetAllocation - strategyData.allocated; + if (amountToRebalance > cashAvailable) { + amountToRebalance = cashAvailable; + } + + uint256 maxDeposit = IERC4626(_strategy).maxDeposit(address(this)); + if (amountToRebalance > maxDeposit) { + amountToRebalance = maxDeposit; + } + + if (amountToRebalance == 0) { + return; // No cash to deposit + } + + isDeposit = true; + } + + if (isDeposit) { // Do required approval (safely) and deposit - IERC20(IERC4626(address(this)).asset()).safeIncreaseAllowance(_strategy, _amountToRebalance); - IERC4626(_strategy).deposit(_amountToRebalance, address(this)); - $.strategies[_strategy].allocated = (strategyData.allocated + _amountToRebalance).toUint120(); - $.totalAllocated += _amountToRebalance; + IERC20(IERC4626(address(this)).asset()).safeIncreaseAllowance(_strategy, amountToRebalance); + IERC4626(_strategy).deposit(amountToRebalance, address(this)); + $.strategies[_strategy].allocated = (strategyData.allocated + amountToRebalance).toUint120(); + $.totalAllocated += amountToRebalance; } else { - IERC4626(_strategy).withdraw(_amountToRebalance, address(this), address(this)); - $.strategies[_strategy].allocated = (strategyData.allocated - _amountToRebalance).toUint120(); - $.totalAllocated -= _amountToRebalance; + IERC4626(_strategy).withdraw(amountToRebalance, address(this), address(this)); + $.strategies[_strategy].allocated = (strategyData.allocated - amountToRebalance).toUint120(); + $.totalAllocated -= amountToRebalance; } - emit Events.Rebalance(_strategy, _amountToRebalance, _isDeposit); + emit Events.Rebalance(_strategy, amountToRebalance, isDeposit); } } diff --git a/src/plugin/Rebalancer.sol b/src/plugin/Rebalancer.sol deleted file mode 100644 index c9e37879..00000000 --- a/src/plugin/Rebalancer.sol +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -// interfaces -import {IEulerAggregationVault} from "../core/interface/IEulerAggregationVault.sol"; -import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; - -/// @title Rebalancer plugin -/// @custom:security-contact security@euler.xyz -/// @author Euler Labs (https://www.eulerlabs.com/) -/// @notice A contract to execute rebalance() on the EulerAggregationVault. -contract Rebalancer { - event ExecuteRebalance( - address indexed curatedVault, - address indexed strategy, - uint256 currentAllocation, - uint256 targetAllocation, - uint256 amountToRebalance - ); - - /// @notice Rebalance strategies allocation for a specific curated vault. - /// @param _aggregationVault Aggregation vault address. - /// @param _strategies Strategies addresses. - function executeRebalance(address _aggregationVault, address[] calldata _strategies) external { - IEulerAggregationVault(_aggregationVault).gulp(); - - for (uint256 i; i < _strategies.length; ++i) { - _rebalance(_aggregationVault, _strategies[i]); - } - } - - /// @dev If current allocation is greater than target allocation, the aggregator will withdraw the excess assets. - /// If current allocation is less than target allocation, the aggregator will: - /// - Try to deposit the delta, if the cash is not sufficient, deposit all the available cash - /// - If all the available cash is greater than the max deposit, deposit the max deposit - /// @param _aggregationVault Aggregation vault address. - /// @param _strategy Strategy address. - function _rebalance(address _aggregationVault, address _strategy) private { - if (_strategy == address(0)) { - return; //nothing to rebalance as that's the cash reserve - } - - IEulerAggregationVault.Strategy memory strategyData = - IEulerAggregationVault(_aggregationVault).getStrategy(_strategy); - - uint256 totalAllocationPointsCache = IEulerAggregationVault(_aggregationVault).totalAllocationPoints(); - uint256 totalAssetsAllocatableCache = IEulerAggregationVault(_aggregationVault).totalAssetsAllocatable(); - uint256 targetAllocation = - totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; - - if ((strategyData.cap > 0) && (targetAllocation > strategyData.cap)) targetAllocation = strategyData.cap; - - uint256 amountToRebalance; - bool isDeposit; - if (strategyData.allocated > targetAllocation) { - // Withdraw - amountToRebalance = strategyData.allocated - targetAllocation; - - uint256 maxWithdraw = IERC4626(_strategy).maxWithdraw(_aggregationVault); - if (amountToRebalance > maxWithdraw) { - amountToRebalance = maxWithdraw; - } - - isDeposit = false; - } else if (strategyData.allocated < targetAllocation) { - // Deposit - uint256 targetCash = totalAssetsAllocatableCache - * IEulerAggregationVault(_aggregationVault).getStrategy(address(0)).allocationPoints - / totalAllocationPointsCache; - uint256 currentCash = - totalAssetsAllocatableCache - IEulerAggregationVault(_aggregationVault).totalAllocated(); - - // Calculate available cash to put in strategies - uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; - - amountToRebalance = targetAllocation - strategyData.allocated; - if (amountToRebalance > cashAvailable) { - amountToRebalance = cashAvailable; - } - - uint256 maxDeposit = IERC4626(_strategy).maxDeposit(_aggregationVault); - if (amountToRebalance > maxDeposit) { - amountToRebalance = maxDeposit; - } - - if (amountToRebalance == 0) { - return; // No cash to deposit - } - - isDeposit = true; - } - - IEulerAggregationVault(_aggregationVault).rebalance(_strategy, amountToRebalance, isDeposit); - - emit ExecuteRebalance(_aggregationVault, _strategy, strategyData.allocated, targetAllocation, amountToRebalance); - } -} diff --git a/test/A16zPropertyTests.t.sol b/test/A16zPropertyTests.t.sol index d28e87db..6e1c50ff 100644 --- a/test/A16zPropertyTests.t.sol +++ b/test/A16zPropertyTests.t.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.0; import {ERC4626Test} from "erc4626-tests/ERC4626.test.sol"; // contracts import {EulerAggregationVault} from "../src/core/EulerAggregationVault.sol"; -import {Rebalancer} from "../src/plugin/Rebalancer.sol"; import {Hooks} from "../src/core/module/Hooks.sol"; import {Rewards} from "../src/core/module/Rewards.sol"; import {Fee} from "../src/core/module/Fee.sol"; @@ -28,7 +27,6 @@ contract A16zPropertyTests is ERC4626Test { Strategy strategyModuleImpl; Rebalance rebalanceModuleImpl; // plugins - Rebalancer rebalancerPlugin; WithdrawalQueue withdrawalQueuePluginImpl; EulerAggregationVaultFactory eulerAggregationVaultFactory; @@ -43,7 +41,6 @@ contract A16zPropertyTests is ERC4626Test { strategyModuleImpl = new Strategy(); rebalanceModuleImpl = new Rebalance(); - rebalancerPlugin = new Rebalancer(); withdrawalQueuePluginImpl = new WithdrawalQueue(); EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ @@ -53,8 +50,7 @@ contract A16zPropertyTests is ERC4626Test { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), strategyModuleImpl: address(strategyModuleImpl), - rebalanceModuleImpl: address(rebalanceModuleImpl), - rebalancer: address(rebalancerPlugin) + rebalanceModuleImpl: address(rebalanceModuleImpl) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); vm.prank(factoryOwner); diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index 42e182aa..db88dc96 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -7,7 +7,6 @@ import {IWithdrawalQueue} from "../../src/core/interface/IWithdrawalQueue.sol"; // contracts import "evk/test/unit/evault/EVaultTestBase.t.sol"; import {EulerAggregationVault, IEulerAggregationVault} from "../../src/core/EulerAggregationVault.sol"; -import {Rebalancer} from "../../src/plugin/Rebalancer.sol"; import {Hooks, HooksModule} from "../../src/core/module/Hooks.sol"; import {Rewards} from "../../src/core/module/Rewards.sol"; import {Fee} from "../../src/core/module/Fee.sol"; @@ -33,7 +32,6 @@ contract EulerAggregationVaultBase is EVaultTestBase { Strategy strategyModuleImpl; Rebalance rebalanceModuleImpl; // plugins - Rebalancer rebalancer; WithdrawalQueue withdrawalQueueImpl; EulerAggregationVaultFactory eulerAggregationVaultFactory; @@ -55,7 +53,6 @@ contract EulerAggregationVaultBase is EVaultTestBase { strategyModuleImpl = new Strategy(); rebalanceModuleImpl = new Rebalance(); - rebalancer = new Rebalancer(); withdrawalQueueImpl = new WithdrawalQueue(); EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ @@ -65,8 +62,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), strategyModuleImpl: address(strategyModuleImpl), - rebalanceModuleImpl: address(rebalanceModuleImpl), - rebalancer: address(rebalancer) + rebalanceModuleImpl: address(rebalanceModuleImpl) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueueImpl)); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 6fd9b8e4..098b043a 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -32,8 +32,7 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), strategyModuleImpl: address(strategyModuleImpl), - rebalanceModuleImpl: address(rebalanceModuleImpl), - rebalancer: address(rebalancer) + rebalanceModuleImpl: address(rebalanceModuleImpl) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueueImpl)); diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index d5646545..a52b257a 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -59,7 +59,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); @@ -141,7 +141,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); @@ -240,7 +240,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq( eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash @@ -330,7 +330,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); @@ -434,7 +434,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq( eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash @@ -566,7 +566,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq( eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash diff --git a/test/e2e/HarvestRedeemE2ETest.t.sol b/test/e2e/HarvestRedeemE2ETest.t.sol index 87c6d1ba..16469565 100644 --- a/test/e2e/HarvestRedeemE2ETest.t.sol +++ b/test/e2e/HarvestRedeemE2ETest.t.sol @@ -52,7 +52,7 @@ contract HarvestRedeemE2ETest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 3ce7d1a7..68dfe381 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -86,7 +86,7 @@ contract PerformanceFeeE2ETest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index 981904d1..53759f1b 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -93,7 +93,7 @@ contract StrategyCapE2ETest is EulerAggregationVaultBase { vm.prank(user1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); @@ -113,7 +113,7 @@ contract StrategyCapE2ETest is EulerAggregationVaultBase { uint256 strategyAllocatedBefore = (eulerAggregationVault.getStrategy(address(eTST))).allocated; strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); vm.stopPrank(); assertEq(strategyAllocatedBefore, (eulerAggregationVault.getStrategy(address(eTST))).allocated); @@ -158,7 +158,7 @@ contract StrategyCapE2ETest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), cap); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), cap); diff --git a/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol index b6ff0a88..6ff76615 100644 --- a/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol +++ b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol @@ -125,7 +125,7 @@ contract ToggleStrategyEmergencyStatusE2ETest is EulerAggregationVaultBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq( eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash @@ -224,7 +224,7 @@ contract ToggleStrategyEmergencyStatusE2ETest is EulerAggregationVaultBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq( eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash diff --git a/test/echidna/CryticERC4626TestsHarness.t.sol b/test/echidna/CryticERC4626TestsHarness.t.sol index e5a708b9..8af4b9c5 100644 --- a/test/echidna/CryticERC4626TestsHarness.t.sol +++ b/test/echidna/CryticERC4626TestsHarness.t.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.0; import {CryticERC4626PropertyTests} from "crytic-properties/ERC4626/ERC4626PropertyTests.sol"; // contracts import {EulerAggregationVault} from "../../src/core/EulerAggregationVault.sol"; -import {Rebalancer} from "../../src/plugin/Rebalancer.sol"; import {Hooks} from "../../src/core/module/Hooks.sol"; import {Rewards} from "../../src/core/module/Rewards.sol"; import {Fee} from "../../src/core/module/Fee.sol"; @@ -27,7 +26,6 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { Strategy strategyModuleImpl; Rebalance rebalanceModuleImpl; // plugins - Rebalancer rebalancerPlugin; WithdrawalQueue withdrawalQueuePluginImpl; EulerAggregationVaultFactory eulerAggregationVaultFactory; @@ -40,7 +38,6 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { strategyModuleImpl = new Strategy(); rebalanceModuleImpl = new Rebalance(); - rebalancerPlugin = new Rebalancer(); withdrawalQueuePluginImpl = new WithdrawalQueue(); EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ @@ -50,8 +47,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), strategyModuleImpl: address(strategyModuleImpl), - rebalanceModuleImpl: address(rebalanceModuleImpl), - rebalancer: address(rebalancerPlugin) + rebalanceModuleImpl: address(rebalanceModuleImpl) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueuePluginImpl)); diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 2b312a9e..2f46e14d 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -52,8 +52,7 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { eulerAggregationVaultHandler = new EulerAggregationVaultHandler(eulerAggregationVault, actorUtil, strategyUtil, withdrawalQueue); - rebalancerHandler = - new RebalancerHandler(eulerAggregationVault, rebalancer, actorUtil, strategyUtil, withdrawalQueue); + rebalancerHandler = new RebalancerHandler(eulerAggregationVault, actorUtil, strategyUtil, withdrawalQueue); withdrawalQueueHandler = new WithdrawalQueueHandler(eulerAggregationVault, actorUtil, strategyUtil, withdrawalQueue); diff --git a/test/invariant/handler/RebalancerHandler.sol b/test/invariant/handler/RebalancerHandler.sol index fa861537..05d9e376 100644 --- a/test/invariant/handler/RebalancerHandler.sol +++ b/test/invariant/handler/RebalancerHandler.sol @@ -12,7 +12,6 @@ import { IEulerAggregationVault, ErrorsLib, IERC4626, - Rebalancer, WithdrawalQueue } from "../../common/EulerAggregationVaultBase.t.sol"; import {Actor} from "../util/Actor.sol"; @@ -22,7 +21,6 @@ contract RebalancerHandler is Test { Actor internal actorUtil; Strategy internal strategyUtil; EulerAggregationVault internal eulerAggVault; - Rebalancer internal rebalancer; WithdrawalQueue internal withdrawalQueue; // last function call state @@ -33,7 +31,6 @@ contract RebalancerHandler is Test { constructor( EulerAggregationVault _eulerAggVault, - Rebalancer _rebalancer, Actor _actor, Strategy _strategy, WithdrawalQueue _withdrawalQueue @@ -41,7 +38,6 @@ contract RebalancerHandler is Test { eulerAggVault = _eulerAggVault; actorUtil = _actor; strategyUtil = _strategy; - rebalancer = _rebalancer; withdrawalQueue = _withdrawalQueue; } @@ -51,8 +47,8 @@ contract RebalancerHandler is Test { (address[] memory strategiesToRebalance, uint256 strategiesCounter) = withdrawalQueue.getWithdrawalQueueArray(); (currentActor, success, returnData) = actorUtil.initiateActorCall( _actorIndexSeed, - address(rebalancer), - abi.encodeWithSelector(Rebalancer.executeRebalance.selector, address(eulerAggVault), strategiesToRebalance) + address(eulerAggVault), + abi.encodeWithSelector(EulerAggregationVault.executeRebalance.selector, strategiesToRebalance) ); for (uint256 i; i < strategiesCounter; i++) { diff --git a/test/invariant/handler/WithdrawalQueueHandler.sol b/test/invariant/handler/WithdrawalQueueHandler.sol index e884af88..0598d090 100644 --- a/test/invariant/handler/WithdrawalQueueHandler.sol +++ b/test/invariant/handler/WithdrawalQueueHandler.sol @@ -13,7 +13,6 @@ import { IEulerAggregationVault, ErrorsLib, IERC4626, - Rebalancer, WithdrawalQueue } from "../../common/EulerAggregationVaultBase.t.sol"; import {Actor} from "../util/Actor.sol"; diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index 85e34967..35f146a5 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -52,7 +52,7 @@ contract GulpTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index 6dda8584..b5165d6e 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -53,7 +53,7 @@ contract HarvestTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index e8d37921..aa20bf7f 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -58,7 +58,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); @@ -107,7 +107,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), eTSTMaxDeposit); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), eTSTMaxDeposit); @@ -142,7 +142,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.warp(block.timestamp + 86400); vm.prank(user1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); // create new strategy & add it IEVault eTSTsecondary; @@ -176,7 +176,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); strategiesToRebalance[0] = address(eTSTsecondary); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); // assertEq(eulerAggregationVault.totalAllocated(), eTSTsecondaryMaxDeposit); assertEq( @@ -226,7 +226,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), strategyBefore.allocated); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); @@ -259,7 +259,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; @@ -277,7 +277,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); @@ -313,7 +313,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; @@ -339,7 +339,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); // TODO: check this // assertEq(eulerAggregationVault.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); @@ -348,11 +348,4 @@ contract RebalanceTest is EulerAggregationVaultBase { // ); // assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated - eTSTMaxWithdraw); } - - function testRebalanceFromRandomSender() public { - vm.startPrank(user1); - vm.expectRevert(ErrorsLib.NotRebalancer.selector); - eulerAggregationVault.rebalance(address(eTST), 1, true); - vm.stopPrank(); - } } diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index 1cd61109..b10a54f9 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -159,7 +159,7 @@ contract RemoveStrategyTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - rebalancer.executeRebalance(address(eulerAggregationVault), strategiesToRebalance); + eulerAggregationVault.executeRebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); diff --git a/test/unit/SetRebalancerTest.t.sol b/test/unit/SetRebalancerTest.t.sol deleted file mode 100644 index b6c50106..00000000 --- a/test/unit/SetRebalancerTest.t.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {EulerAggregationVaultBase, EulerAggregationVault, ErrorsLib} from "../common/EulerAggregationVaultBase.t.sol"; - -contract SetRebalancerTest is EulerAggregationVaultBase { - function setUp() public virtual override { - super.setUp(); - } - - function testSetInvalidRebalancer() public { - vm.startPrank(manager); - vm.expectRevert(ErrorsLib.InvalidRebalancerPlugin.selector); - eulerAggregationVault.setRebalancer(address(0)); - } - - function testSetRebalancer() public { - address newRebalancer = makeAddr("Rebalancer"); - - vm.prank(manager); - eulerAggregationVault.setRebalancer(newRebalancer); - - assertEq(eulerAggregationVault.rebalancer(), newRebalancer); - } -} From 3fe8e395b413299598a518abd648998516cacf1a Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 19 Jul 2024 17:41:40 +0300 Subject: [PATCH 265/316] clean --- src/core/Dispatch.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/Dispatch.sol b/src/core/Dispatch.sol index f1cec2dd..2f9bc1f9 100644 --- a/src/core/Dispatch.sol +++ b/src/core/Dispatch.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.0; //contracts -import {Shared} from "./common/Shared.sol"; import {HooksModule} from "./module/Hooks.sol"; import {RewardsModule} from "./module/Rewards.sol"; import {StrategyModule} from "./module/Strategy.sol"; From 299949e9d4b98070b3441cb2cd872d184cf2cde8 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 19 Jul 2024 17:55:39 +0300 Subject: [PATCH 266/316] update modifiers order --- src/core/EulerAggregationVault.sol | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 0f489cf1..12c0c525 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -17,7 +17,6 @@ import {ERC20VotesUpgradeable} from "@openzeppelin-upgradeable/token/ERC20/exten import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {Shared} from "./common/Shared.sol"; -import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgradeable.sol"; // libs import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; @@ -161,30 +160,30 @@ contract EulerAggregationVault is function addStrategy(address _strategy, uint256 _allocationPoints) external override - use(strategyModule) onlyRole(STRATEGY_OPERATOR) + use(strategyModule) {} /// @dev See {StrategyModule-removeStrategy}. - function removeStrategy(address _strategy) external override use(strategyModule) onlyRole(STRATEGY_OPERATOR) {} + function removeStrategy(address _strategy) external override onlyRole(STRATEGY_OPERATOR) use(strategyModule) {} /// @dev See {StrategyModule-setStrategyCap}. - function setStrategyCap(address _strategy, uint256 _cap) external override use(strategyModule) onlyRole(GUARDIAN) {} + function setStrategyCap(address _strategy, uint256 _cap) external override onlyRole(GUARDIAN) use(strategyModule) {} /// @dev See {StrategyModule-adjustAllocationPoints}. function adjustAllocationPoints(address _strategy, uint256 _newPoints) external override - use(strategyModule) onlyRole(GUARDIAN) + use(strategyModule) {} /// @dev See {StrategyModule-toggleStrategyEmergencyStatus}. function toggleStrategyEmergencyStatus(address _strategy) external override - use(strategyModule) onlyRole(GUARDIAN) + use(strategyModule) {} /// @dev See {RewardsModule-enableBalanceForwarder}. From daaa6bd19c88f54577a5e6b82ae012f72b72cb60 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 19 Jul 2024 19:30:50 +0300 Subject: [PATCH 267/316] add _gulp() into Shared.sol --- src/core/EulerAggregationVault.sol | 61 --------------------------- src/core/common/Shared.sol | 66 ++++++++++++++++++++++++++++++ src/core/module/Rebalance.sol | 9 ++-- 3 files changed, 70 insertions(+), 66 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 12c0c525..880728e4 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -49,11 +49,6 @@ contract EulerAggregationVault is bytes32 public constant AGGREGATION_VAULT_MANAGER = keccak256("AGGREGATION_VAULT_MANAGER"); bytes32 public constant AGGREGATION_VAULT_MANAGER_ADMIN = keccak256("AGGREGATION_VAULT_MANAGER_ADMIN"); - /// @dev Interest rate smearing period - uint256 public constant INTEREST_SMEAR = 2 weeks; - /// @dev Minimum amount of shares to exist for gulp to be enabled - uint256 public constant MIN_SHARES_FOR_GULP = 1e7; - /// @dev Constructor. constructor(ConstructorParams memory _constructorParams) Dispatch( @@ -425,40 +420,6 @@ contract EulerAggregationVault is ); } - /// @notice update accrued interest. - function _updateInterestAccrued() internal { - uint256 accruedInterest = _interestAccruedFromCache(); - - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - // it's safe to down-cast because the accrued interest is a fraction of interest left - $.interestLeft -= uint168(accruedInterest); - $.lastInterestUpdate = uint40(block.timestamp); - - // Move interest accrued to totalAssetsDeposited - $.totalAssetsDeposited += accruedInterest; - } - - /// @dev gulp positive yield into interest left amd update accrued interest. - function _gulp() internal { - _updateInterestAccrued(); - - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - - // Do not gulp if total supply is too low - if (totalSupply() < MIN_SHARES_FOR_GULP) return; - - uint256 toGulp = totalAssetsAllocatable() - $.totalAssetsDeposited - $.interestLeft; - if (toGulp == 0) return; - - uint256 maxGulp = type(uint168).max - $.interestLeft; - if (toGulp > maxGulp) toGulp = maxGulp; // cap interest, allowing the vault to function - - $.interestSmearEnd = uint40(block.timestamp + INTEREST_SMEAR); - $.interestLeft += uint168(toGulp); // toGulp <= maxGulp <= max uint168 - - emit Events.Gulp($.interestLeft, $.interestSmearEnd); - } - /// @dev Loop through stratgies, aggregate positive yield and loss and account for net amount. /// @dev Loss socialization will be taken out from interest left first, if not enough, sozialize on deposits. function _harvest() internal { @@ -573,28 +534,6 @@ contract EulerAggregationVault is } } - /// @dev Get accrued interest without updating it. - /// @return uint256 Accrued interest. - function _interestAccruedFromCache() internal view returns (uint256) { - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - - // If distribution ended, full amount is accrued - if (block.timestamp >= $.interestSmearEnd) { - return $.interestLeft; - } - - // If just updated return 0 - if ($.lastInterestUpdate == block.timestamp) { - return 0; - } - - // Else return what has accrued - uint256 totalDuration = $.interestSmearEnd - $.lastInterestUpdate; - uint256 timePassed = block.timestamp - $.lastInterestUpdate; - - return $.interestLeft * timePassed / totalDuration; - } - /// @dev Check if caller is WithdrawalQueue address, if not revert. function _isCallerWithdrawalQueue() internal view { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); diff --git a/src/core/common/Shared.sol b/src/core/common/Shared.sol index b7054802..6b7e2f58 100644 --- a/src/core/common/Shared.sol +++ b/src/core/common/Shared.sol @@ -3,10 +3,14 @@ pragma solidity ^0.8.0; // interfaces import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; +import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {IEulerAggregationVault} from "../interface/IEulerAggregationVault.sol"; // libs import {HooksLib} from "../lib/HooksLib.sol"; import {StorageLib as Storage, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; +import {EventsLib as Events} from "../lib/EventsLib.sol"; /// @title Shared contract /// @custom:security-contact security@euler.xyz @@ -27,6 +31,11 @@ abstract contract Shared { uint32 constant ACTIONS_COUNTER = 1 << 6; uint256 constant HOOKS_MASK = 0x00000000000000000000000000000000000000000000000000000000FFFFFFFF; + /// @dev Interest rate smearing period + uint256 public constant INTEREST_SMEAR = 2 weeks; + /// @dev Minimum amount of shares to exist for gulp to be enabled + uint256 public constant MIN_SHARES_FOR_GULP = 1e7; + modifier nonReentrant() { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); @@ -88,6 +97,63 @@ abstract contract Shared { return (address(uint160(_hooksConfig >> 32)), uint32(_hooksConfig & HOOKS_MASK)); } + /// @dev gulp positive yield into interest left amd update accrued interest. + function _gulp() internal { + _updateInterestAccrued(); + + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + + // Do not gulp if total supply is too low + if (IERC4626(address(this)).totalSupply() < MIN_SHARES_FOR_GULP) return; + + uint256 toGulp = + IEulerAggregationVault(address(this)).totalAssetsAllocatable() - $.totalAssetsDeposited - $.interestLeft; + if (toGulp == 0) return; + + uint256 maxGulp = type(uint168).max - $.interestLeft; + if (toGulp > maxGulp) toGulp = maxGulp; // cap interest, allowing the vault to function + + $.interestSmearEnd = uint40(block.timestamp + INTEREST_SMEAR); + $.interestLeft += uint168(toGulp); // toGulp <= maxGulp <= max uint168 + + emit Events.Gulp($.interestLeft, $.interestSmearEnd); + } + + /// @notice update accrued interest. + function _updateInterestAccrued() internal { + uint256 accruedInterest = _interestAccruedFromCache(); + + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + // it's safe to down-cast because the accrued interest is a fraction of interest left + $.interestLeft -= uint168(accruedInterest); + $.lastInterestUpdate = uint40(block.timestamp); + + // Move interest accrued to totalAssetsDeposited + $.totalAssetsDeposited += accruedInterest; + } + + /// @dev Get accrued interest without updating it. + /// @return uint256 Accrued interest. + function _interestAccruedFromCache() internal view returns (uint256) { + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + + // If distribution ended, full amount is accrued + if (block.timestamp >= $.interestSmearEnd) { + return $.interestLeft; + } + + // If just updated return 0 + if ($.lastInterestUpdate == block.timestamp) { + return 0; + } + + // Else return what has accrued + uint256 totalDuration = $.interestSmearEnd - $.lastInterestUpdate; + uint256 timePassed = block.timestamp - $.lastInterestUpdate; + + return $.interestLeft * timePassed / totalDuration; + } + /// @dev Revert with call error or EmptyError /// @param _errorMsg call revert message function _revertBytes(bytes memory _errorMsg) private pure { diff --git a/src/core/module/Rebalance.sol b/src/core/module/Rebalance.sol index b174ea04..260f4864 100644 --- a/src/core/module/Rebalance.sol +++ b/src/core/module/Rebalance.sol @@ -22,7 +22,7 @@ abstract contract RebalanceModule is ContextUpgradeable, Shared { /// @notice Rebalance strategies allocation for a specific curated vault. /// @param _strategies Strategies addresses. function executeRebalance(address[] calldata _strategies) external virtual nonReentrant { - // IEulerAggregationVault(_aggregationVault).gulp(); + _gulp(); for (uint256 i; i < _strategies.length; ++i) { _rebalance(_strategies[i]); @@ -67,10 +67,9 @@ abstract contract RebalanceModule is ContextUpgradeable, Shared { isDeposit = false; } else if (strategyData.allocated < targetAllocation) { // Deposit - uint256 targetCash = totalAssetsAllocatableCache - * IEulerAggregationVault(address(this)).getStrategy(address(0)).allocationPoints - / totalAllocationPointsCache; - uint256 currentCash = totalAssetsAllocatableCache - IEulerAggregationVault(address(this)).totalAllocated(); + uint256 targetCash = + totalAssetsAllocatableCache * $.strategies[address(0)].allocationPoints / totalAllocationPointsCache; + uint256 currentCash = totalAssetsAllocatableCache - $.totalAllocated; // Calculate available cash to put in strategies uint256 cashAvailable = (currentCash > targetCash) ? currentCash - targetCash : 0; From 55ea44fd9f0d4dceed13f79bc9ebb0a11b48010c Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Sun, 21 Jul 2024 16:33:31 +0300 Subject: [PATCH 268/316] remove whitelisting of WithdrawalQueue --- src/core/EulerAggregationVaultFactory.sol | 75 +++++--------------- test/A16zPropertyTests.t.sol | 2 +- test/common/EulerAggregationVaultBase.t.sol | 60 ++++++++-------- test/e2e/BalanceForwarderE2ETest.t.sol | 2 +- test/echidna/CryticERC4626TestsHarness.t.sol | 2 +- 5 files changed, 51 insertions(+), 90 deletions(-) diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index 5a363c83..21af9f82 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -3,14 +3,13 @@ pragma solidity ^0.8.0; // contracts import {EulerAggregationVault, IEulerAggregationVault} from "./EulerAggregationVault.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; // libs import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; /// @title EulerAggregationVaultFactory contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) -contract EulerAggregationVaultFactory is Ownable { +contract EulerAggregationVaultFactory { error WithdrawalQueueAlreadyWhitelisted(); error NotWhitelistedWithdrawalQueueImpl(); error InvalidQuery(); @@ -18,16 +17,12 @@ contract EulerAggregationVaultFactory is Ownable { /// core dependencies address public immutable balanceTracker; /// core modules implementations addresses - address public immutable rewardsModuleImpl; - address public immutable hooksModuleImpl; - address public immutable feeModuleImpl; - address public immutable strategyModuleImpl; - address public immutable rebalanceModuleImpl; - /// @dev Mapping to set whitelisted WhithdrawalQueue plugin implementation. - mapping(address => bool) public isWhitelistedWithdrawalQueueImpl; - /// @dev An array of the whitelisted WithdrawalQueue plugin implementations. - address[] public withdrawalQueueImpls; - /// @dev Array to store the addresses of all deployed aggregation vaults. + address public immutable rewardsModule; + address public immutable hooksModule; + address public immutable feeModule; + address public immutable strategyModule; + address public immutable rebalanceModule; + address[] public aggregationVaults; /// @dev Init params struct. @@ -48,26 +43,13 @@ contract EulerAggregationVaultFactory is Ownable { /// @notice Constructor. /// @param _factoryParams FactoryParams struct. - constructor(FactoryParams memory _factoryParams) Ownable(_factoryParams.owner) { + constructor(FactoryParams memory _factoryParams) { balanceTracker = _factoryParams.balanceTracker; - rewardsModuleImpl = _factoryParams.rewardsModuleImpl; - hooksModuleImpl = _factoryParams.hooksModuleImpl; - feeModuleImpl = _factoryParams.feeModuleImpl; - strategyModuleImpl = _factoryParams.strategyModuleImpl; - rebalanceModuleImpl = _factoryParams.rebalanceModuleImpl; - } - - /// @notice Whitelist a new WithdrawalQueue implementation. - /// @dev Can only be called by factory owner. - /// @param _withdrawalQueuImpl WithdrawalQueue plugin implementation. - function whitelistWithdrawalQueueImpl(address _withdrawalQueuImpl) external onlyOwner { - if (isWhitelistedWithdrawalQueueImpl[_withdrawalQueuImpl]) revert WithdrawalQueueAlreadyWhitelisted(); - - isWhitelistedWithdrawalQueueImpl[_withdrawalQueuImpl] = true; - - withdrawalQueueImpls.push(_withdrawalQueuImpl); - - emit WhitelistWithdrawalQueueImpl(_withdrawalQueuImpl); + rewardsModule = Clones.clone(_factoryParams.rewardsModuleImpl); + hooksModule = Clones.clone(_factoryParams.hooksModuleImpl); + feeModule = Clones.clone(_factoryParams.feeModuleImpl); + strategyModule = Clones.clone(_factoryParams.strategyModuleImpl); + rebalanceModule = Clones.clone(_factoryParams.rebalanceModuleImpl); } /// @notice Deploy a new aggregation vault. @@ -86,16 +68,14 @@ contract EulerAggregationVaultFactory is Ownable { string memory _symbol, uint256 _initialCashAllocationPoints ) external returns (address) { - if (!isWhitelistedWithdrawalQueueImpl[_withdrawalQueueImpl]) revert NotWhitelistedWithdrawalQueueImpl(); - // deploy new aggregation vault IEulerAggregationVault.ConstructorParams memory aggregationVaultConstructorParams = IEulerAggregationVault .ConstructorParams({ - rewardsModule: Clones.clone(rewardsModuleImpl), - hooksModule: Clones.clone(hooksModuleImpl), - feeModule: Clones.clone(feeModuleImpl), - strategyModule: Clones.clone(strategyModuleImpl), - rebalanceModule: Clones.clone(rebalanceModuleImpl) + rewardsModule: rewardsModule, + hooksModule: hooksModule, + feeModule: feeModule, + strategyModule: strategyModule, + rebalanceModule: rebalanceModule }); EulerAggregationVault eulerAggregationVault = new EulerAggregationVault(aggregationVaultConstructorParams); @@ -117,25 +97,6 @@ contract EulerAggregationVaultFactory is Ownable { return address(eulerAggregationVault); } - /// @notice Fetch the length of the whitelisted WithdrawalQueue implementations list - /// @return The length of the proxy list array - function getWithdrawalQueueImplsListLength() external view returns (uint256) { - return withdrawalQueueImpls.length; - } - - /// @notice Return the WithdrawalQueue implementations list. - /// @return withdrawalQueueImplsList An array of addresses. - function getWithdrawalQueueImplsList() external view returns (address[] memory) { - uint256 length = withdrawalQueueImpls.length; - address[] memory withdrawalQueueImplsList = new address[](length); - - for (uint256 i; i < length; ++i) { - withdrawalQueueImplsList[i] = withdrawalQueueImpls[i]; - } - - return withdrawalQueueImplsList; - } - /// @notice Fetch the length of the deployed aggregation vaults list. /// @return The length of the aggregation vaults list array. function getAggregationVaultsListLength() external view returns (uint256) { diff --git a/test/A16zPropertyTests.t.sol b/test/A16zPropertyTests.t.sol index 6e1c50ff..91de5c7e 100644 --- a/test/A16zPropertyTests.t.sol +++ b/test/A16zPropertyTests.t.sol @@ -54,7 +54,7 @@ contract A16zPropertyTests is ERC4626Test { }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); vm.prank(factoryOwner); - eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueuePluginImpl)); + // eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueuePluginImpl)); _underlying_ = address(new ERC20Mock()); _vault_ = eulerAggregationVaultFactory.deployEulerAggregationVault( diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index db88dc96..eb909388 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -65,7 +65,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { rebalanceModuleImpl: address(rebalanceModuleImpl) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); - eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueueImpl)); + // eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueueImpl)); eulerAggregationVault = EulerAggregationVault( eulerAggregationVaultFactory.deployEulerAggregationVault( @@ -129,10 +129,10 @@ contract EulerAggregationVaultBase is EVaultTestBase { assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager)); assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); - assertEq(eulerAggregationVaultFactory.getWithdrawalQueueImplsListLength(), 1); - address[] memory withdrawalQueueList = eulerAggregationVaultFactory.getWithdrawalQueueImplsList(); - assertEq(withdrawalQueueList.length, 1); - assertEq(address(withdrawalQueueList[0]), address(withdrawalQueueImpl)); + // assertEq(eulerAggregationVaultFactory.getWithdrawalQueueImplsListLength(), 1); + // address[] memory withdrawalQueueList = eulerAggregationVaultFactory.getWithdrawalQueueImplsList(); + // assertEq(withdrawalQueueList.length, 1); + // assertEq(address(withdrawalQueueList[0]), address(withdrawalQueueImpl)); assertEq(eulerAggregationVaultFactory.getAggregationVaultsListLength(), 1); address[] memory aggregationVaultsList = eulerAggregationVaultFactory.getAggregationVaultsListSlice(0, 1); @@ -140,38 +140,38 @@ contract EulerAggregationVaultBase is EVaultTestBase { assertEq(address(aggregationVaultsList[0]), address(eulerAggregationVault)); } - function testWhitelistWithdrawalQueueImpl() public { - address newWithdrawalQueueImpl = makeAddr("NEW_WITHDRAWAL_QUEUE_IMPL"); + // function testWhitelistWithdrawalQueueImpl() public { + // address newWithdrawalQueueImpl = makeAddr("NEW_WITHDRAWAL_QUEUE_IMPL"); - vm.prank(deployer); - eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(newWithdrawalQueueImpl); + // vm.prank(deployer); + // eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(newWithdrawalQueueImpl); - assertEq(eulerAggregationVaultFactory.isWhitelistedWithdrawalQueueImpl(newWithdrawalQueueImpl), true); - address[] memory withdrawalQueueList = eulerAggregationVaultFactory.getWithdrawalQueueImplsList(); - assertEq(withdrawalQueueList.length, 2); - assertEq(address(withdrawalQueueList[1]), address(newWithdrawalQueueImpl)); + // assertEq(eulerAggregationVaultFactory.isWhitelistedWithdrawalQueueImpl(newWithdrawalQueueImpl), true); + // address[] memory withdrawalQueueList = eulerAggregationVaultFactory.getWithdrawalQueueImplsList(); + // assertEq(withdrawalQueueList.length, 2); + // assertEq(address(withdrawalQueueList[1]), address(newWithdrawalQueueImpl)); - vm.prank(deployer); - vm.expectRevert(EulerAggregationVaultFactory.WithdrawalQueueAlreadyWhitelisted.selector); - eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(newWithdrawalQueueImpl); - } + // vm.prank(deployer); + // vm.expectRevert(EulerAggregationVaultFactory.WithdrawalQueueAlreadyWhitelisted.selector); + // eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(newWithdrawalQueueImpl); + // } - function testDeployEulerAggregationVaultWithRandomWithdrawalQueue() public { - address newWithdrawalQueueImpl = makeAddr("NEW_WITHDRAWAL_QUEUE_IMPL"); + // function testDeployEulerAggregationVaultWithRandomWithdrawalQueue() public { + // address newWithdrawalQueueImpl = makeAddr("NEW_WITHDRAWAL_QUEUE_IMPL"); - vm.expectRevert(EulerAggregationVaultFactory.NotWhitelistedWithdrawalQueueImpl.selector); - eulerAggregationVaultFactory.deployEulerAggregationVault( - newWithdrawalQueueImpl, address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS - ); + // vm.expectRevert(EulerAggregationVaultFactory.NotWhitelistedWithdrawalQueueImpl.selector); + // eulerAggregationVaultFactory.deployEulerAggregationVault( + // newWithdrawalQueueImpl, address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS + // ); - address[] memory aggregationVaultsList = - eulerAggregationVaultFactory.getAggregationVaultsListSlice(0, type(uint256).max); - assertEq(aggregationVaultsList.length, 1); - assertEq(address(aggregationVaultsList[0]), address(eulerAggregationVault)); + // address[] memory aggregationVaultsList = + // eulerAggregationVaultFactory.getAggregationVaultsListSlice(0, type(uint256).max); + // assertEq(aggregationVaultsList.length, 1); + // assertEq(address(aggregationVaultsList[0]), address(eulerAggregationVault)); - vm.expectRevert(EulerAggregationVaultFactory.InvalidQuery.selector); - eulerAggregationVaultFactory.getAggregationVaultsListSlice(1, 0); - } + // vm.expectRevert(EulerAggregationVaultFactory.InvalidQuery.selector); + // eulerAggregationVaultFactory.getAggregationVaultsListSlice(1, 0); + // } function testDeployEulerAggregationVaultWithInvalidInitialCashAllocationPoints() public { vm.expectRevert(ErrorsLib.InitialAllocationPointsZero.selector); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index 098b043a..aabd1fda 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -35,7 +35,7 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { rebalanceModuleImpl: address(rebalanceModuleImpl) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); - eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueueImpl)); + // eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueueImpl)); eulerAggregationVault = EulerAggregationVault( eulerAggregationVaultFactory.deployEulerAggregationVault( diff --git a/test/echidna/CryticERC4626TestsHarness.t.sol b/test/echidna/CryticERC4626TestsHarness.t.sol index 8af4b9c5..92b72bc9 100644 --- a/test/echidna/CryticERC4626TestsHarness.t.sol +++ b/test/echidna/CryticERC4626TestsHarness.t.sol @@ -50,7 +50,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { rebalanceModuleImpl: address(rebalanceModuleImpl) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); - eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueuePluginImpl)); + // eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueuePluginImpl)); TestERC20Token _asset = new TestERC20Token("Test Token", "TT", 18); address _vault = eulerAggregationVaultFactory.deployEulerAggregationVault( From f61b867f3408838a87664c3d853aec31c684b4c7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Sun, 21 Jul 2024 17:07:30 +0300 Subject: [PATCH 269/316] Refactor factory --- src/core/EulerAggregationVaultFactory.sol | 28 ++++++++++--------- src/core/interface/IEulerAggregationVault.sol | 1 + 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index 21af9f82..a57da1c2 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -22,6 +22,8 @@ contract EulerAggregationVaultFactory { address public immutable feeModule; address public immutable strategyModule; address public immutable rebalanceModule; + /// aggregation vault implementation address + address public immutable aggregationVaultImpl; address[] public aggregationVaults; @@ -36,7 +38,6 @@ contract EulerAggregationVaultFactory { address rebalanceModuleImpl; } - event WhitelistWithdrawalQueueImpl(address withdrawalQueueImpl); event DeployEulerAggregationVault( address indexed _owner, address _aggregationVault, address indexed _withdrawalQueueImpl, address indexed _asset ); @@ -50,6 +51,16 @@ contract EulerAggregationVaultFactory { feeModule = Clones.clone(_factoryParams.feeModuleImpl); strategyModule = Clones.clone(_factoryParams.strategyModuleImpl); rebalanceModule = Clones.clone(_factoryParams.rebalanceModuleImpl); + + IEulerAggregationVault.ConstructorParams memory aggregationVaultConstructorParams = IEulerAggregationVault + .ConstructorParams({ + rewardsModule: rewardsModule, + hooksModule: hooksModule, + feeModule: feeModule, + strategyModule: strategyModule, + rebalanceModule: rebalanceModule + }); + aggregationVaultImpl = address(new EulerAggregationVault(aggregationVaultConstructorParams)); } /// @notice Deploy a new aggregation vault. @@ -68,16 +79,7 @@ contract EulerAggregationVaultFactory { string memory _symbol, uint256 _initialCashAllocationPoints ) external returns (address) { - // deploy new aggregation vault - IEulerAggregationVault.ConstructorParams memory aggregationVaultConstructorParams = IEulerAggregationVault - .ConstructorParams({ - rewardsModule: rewardsModule, - hooksModule: hooksModule, - feeModule: feeModule, - strategyModule: strategyModule, - rebalanceModule: rebalanceModule - }); - EulerAggregationVault eulerAggregationVault = new EulerAggregationVault(aggregationVaultConstructorParams); + address eulerAggregationVault = Clones.clone(aggregationVaultImpl); IEulerAggregationVault.InitParams memory aggregationVaultInitParams = IEulerAggregationVault.InitParams({ aggregationVaultOwner: msg.sender, @@ -88,13 +90,13 @@ contract EulerAggregationVaultFactory { symbol: _symbol, initialCashAllocationPoints: _initialCashAllocationPoints }); - eulerAggregationVault.init(aggregationVaultInitParams); + IEulerAggregationVault(eulerAggregationVault).init(aggregationVaultInitParams); aggregationVaults.push(address(eulerAggregationVault)); emit DeployEulerAggregationVault(msg.sender, address(eulerAggregationVault), _withdrawalQueueImpl, _asset); - return address(eulerAggregationVault); + return eulerAggregationVault; } /// @notice Fetch the length of the deployed aggregation vaults list. diff --git a/src/core/interface/IEulerAggregationVault.sol b/src/core/interface/IEulerAggregationVault.sol index 00d9ab8c..ca3f5160 100644 --- a/src/core/interface/IEulerAggregationVault.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -59,6 +59,7 @@ interface IEulerAggregationVault { Emergency } + function init(InitParams calldata _initParams) external; function gulp() external; function harvest() external; function executeStrategyWithdraw(address _strategy, uint256 _withdrawAmount) external returns (uint256); From 0c95eb2152a3823167d7f0d60474bad174e73c39 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Sun, 21 Jul 2024 17:09:16 +0300 Subject: [PATCH 270/316] chore: add echidna to CI --- .github/workflows/echidna.yml | 62 +++++++++++++++++------------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml index d40c1931..880c90da 100644 --- a/.github/workflows/echidna.yml +++ b/.github/workflows/echidna.yml @@ -1,38 +1,38 @@ -# name: Echidna Test +name: Echidna Test -# on: -# push: -# branches: -# - main -# pull_request: +on: + push: + branches: + - main + pull_request: -# env: -# FOUNDRY_PROFILE: ci +env: + FOUNDRY_PROFILE: ci -# concurrency: -# group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} -# cancel-in-progress: true +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true -# jobs: -# test: -# runs-on: ubuntu-latest -# steps: -# - name: Checkout repository -# uses: actions/checkout@v3 -# with: -# submodules: recursive +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + submodules: recursive -# - name: Install Foundry -# uses: foundry-rs/foundry-toolchain@v1 -# with: -# version: nightly + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly -# - name: Compile contracts -# run: | -# forge build --build-info + - name: Compile contracts + run: | + forge build --build-info -# - name: Run Echidna -# uses: crytic/echidna-action@v2 -# with: -# files: test/echidna/CryticERC4626Harness.t.sol -# contract: CryticERC4626Harness \ No newline at end of file + - name: Run Echidna + uses: crytic/echidna-action@v2 + with: + files: test/echidna/ + contract: CryticERC4626Harness \ No newline at end of file From 94b5414bd1830a8135be3249d90181879ede1480 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 22 Jul 2024 12:28:39 +0300 Subject: [PATCH 271/316] add gulping --- src/core/EulerAggregationVault.sol | 15 +++-- src/core/lib/ErrorsLib.sol | 1 + .../EulerAggregationLayerInvariants.t.sol | 4 -- .../handler/EulerAggregationVaultHandler.sol | 18 ++++++ test/invariant/handler/RebalancerHandler.sol | 61 ------------------- 5 files changed, 30 insertions(+), 69 deletions(-) delete mode 100644 test/invariant/handler/RebalancerHandler.sol diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 880728e4..7148c59e 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -32,8 +32,8 @@ import {EventsLib as Events} from "./lib/EventsLib.sol"; /// @dev Do NOT use with rebasing tokens /// @dev inspired by Yearn v3 ❤️ contract EulerAggregationVault is - ERC4626Upgradeable, ERC20VotesUpgradeable, + ERC4626Upgradeable, AccessControlEnumerableUpgradeable, Dispatch, IEulerAggregationVault @@ -189,7 +189,7 @@ contract EulerAggregationVault is function executeRebalance(address[] calldata _strategies) external override use(rebalanceModule) {} - /// @notice Harvest all the strategies. + /// @notice Harvest all the strategies. Any positive yiled should be gupled by calling gulp() after harvesting. /// @dev This function will loop through the strategies following the withdrawal queue order and harvest all. /// @dev Harvest yield and losses will be aggregated and only net yield/loss will be accounted. function harvest() external nonReentrant { @@ -245,6 +245,8 @@ contract EulerAggregationVault is ) external { _isCallerWithdrawalQueue(); + if (_receiver == address(this)) revert Errors.CanNotReceiveWithdrawnAsset(); + super._withdraw(_caller, _receiver, _owner, _assets, _shares); AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); @@ -423,6 +425,9 @@ contract EulerAggregationVault is /// @dev Loop through stratgies, aggregate positive yield and loss and account for net amount. /// @dev Loss socialization will be taken out from interest left first, if not enough, sozialize on deposits. function _harvest() internal { + // gulp any extra tokens to cover in case of loss socialization + _gulp(); + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); (address[] memory withdrawalQueueArray, uint256 length) = @@ -440,12 +445,14 @@ contract EulerAggregationVault is $.totalAllocated = $.totalAllocated + totalYield - totalLoss; if (totalLoss > totalYield) { + // we do not need to call gulp() again here as aggregated yield is negative and there is nothing gulpable. _deductLoss(totalLoss - totalYield); + } else { + // gulp net positive yield + _gulp(); } emit Events.Harvest($.totalAllocated, totalYield, totalLoss); - - _gulp(); } /// @dev Execute harvest on a single strategy. diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index 828baf67..93545216 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -28,4 +28,5 @@ library ErrorsLib { error CanNotAdjustAllocationPoints(); error CanNotToggleStrategyEmergencyStatus(); error CanNotRemoveStrategyInEmergencyStatus(); + error CanNotReceiveWithdrawnAsset(); } diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 2f46e14d..61b92237 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -12,7 +12,6 @@ import { import {Actor} from "./util/Actor.sol"; import {Strategy} from "./util/Strategy.sol"; import {EulerAggregationVaultHandler} from "./handler/EulerAggregationVaultHandler.sol"; -import {RebalancerHandler} from "./handler/RebalancerHandler.sol"; import {WithdrawalQueueHandler} from "./handler/WithdrawalQueueHandler.sol"; contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { @@ -20,7 +19,6 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { Strategy internal strategyUtil; EulerAggregationVaultHandler internal eulerAggregationVaultHandler; - RebalancerHandler internal rebalancerHandler; WithdrawalQueueHandler internal withdrawalQueueHandler; // other strategies @@ -52,12 +50,10 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { eulerAggregationVaultHandler = new EulerAggregationVaultHandler(eulerAggregationVault, actorUtil, strategyUtil, withdrawalQueue); - rebalancerHandler = new RebalancerHandler(eulerAggregationVault, actorUtil, strategyUtil, withdrawalQueue); withdrawalQueueHandler = new WithdrawalQueueHandler(eulerAggregationVault, actorUtil, strategyUtil, withdrawalQueue); targetContract(address(eulerAggregationVaultHandler)); - targetContract(address(rebalancerHandler)); targetContract(address(withdrawalQueueHandler)); } diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index c96cfbe7..204d93a6 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -186,6 +186,24 @@ contract EulerAggregationVaultHandler is Test { assertEq(eulerAggVault.totalAllocationPoints(), ghost_totalAllocationPoints); } + function executeRebalance(uint256 _actorIndexSeed) external { + (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); + + (address[] memory strategiesToRebalance, uint256 strategiesCounter) = withdrawalQueue.getWithdrawalQueueArray(); + (currentActor, success, returnData) = actorUtil.initiateActorCall( + _actorIndexSeed, + address(eulerAggVault), + abi.encodeWithSelector(EulerAggregationVault.executeRebalance.selector, strategiesToRebalance) + ); + + for (uint256 i; i < strategiesCounter; i++) { + assertEq( + IERC4626(strategiesToRebalance[i]).maxWithdraw(address(eulerAggVault)), + (eulerAggVault.getStrategy(strategiesToRebalance[i])).allocated + ); + } + } + function harvest(uint256 _actorIndexSeed) external { // track total yield and total loss to simulate loss socialization uint256 totalYield; diff --git a/test/invariant/handler/RebalancerHandler.sol b/test/invariant/handler/RebalancerHandler.sol deleted file mode 100644 index 05d9e376..00000000 --- a/test/invariant/handler/RebalancerHandler.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import { - Test, - EulerAggregationVaultBase, - EulerAggregationVault, - EVault, - IEVault, - IRMTestDefault, - TestERC20, - IEulerAggregationVault, - ErrorsLib, - IERC4626, - WithdrawalQueue -} from "../../common/EulerAggregationVaultBase.t.sol"; -import {Actor} from "../util/Actor.sol"; -import {Strategy} from "../util/Strategy.sol"; - -contract RebalancerHandler is Test { - Actor internal actorUtil; - Strategy internal strategyUtil; - EulerAggregationVault internal eulerAggVault; - WithdrawalQueue internal withdrawalQueue; - - // last function call state - address currentActor; - uint256 currentActorIndex; - bool success; - bytes returnData; - - constructor( - EulerAggregationVault _eulerAggVault, - Actor _actor, - Strategy _strategy, - WithdrawalQueue _withdrawalQueue - ) { - eulerAggVault = _eulerAggVault; - actorUtil = _actor; - strategyUtil = _strategy; - withdrawalQueue = _withdrawalQueue; - } - - function executeRebalance(uint256 _actorIndexSeed) external { - (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); - - (address[] memory strategiesToRebalance, uint256 strategiesCounter) = withdrawalQueue.getWithdrawalQueueArray(); - (currentActor, success, returnData) = actorUtil.initiateActorCall( - _actorIndexSeed, - address(eulerAggVault), - abi.encodeWithSelector(EulerAggregationVault.executeRebalance.selector, strategiesToRebalance) - ); - - for (uint256 i; i < strategiesCounter; i++) { - assertEq( - IERC4626(strategiesToRebalance[i]).maxWithdraw(address(eulerAggVault)), - (eulerAggVault.getStrategy(strategiesToRebalance[i])).allocated - ); - } - } -} From 585bce39d29e5252f4a744891fb2eea42fcbc8e2 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 22 Jul 2024 12:50:21 +0300 Subject: [PATCH 272/316] fix --- src/core/module/Strategy.sol | 5 +++- .../EulerAggregationLayerInvariants.t.sol | 24 ++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/core/module/Strategy.sol b/src/core/module/Strategy.sol index b0efdca1..38a80003 100644 --- a/src/core/module/Strategy.sol +++ b/src/core/module/Strategy.sol @@ -85,10 +85,13 @@ abstract contract StrategyModule is Shared { _deductLoss(strategyCached.allocated); } else { + uint256 vaultStrategyBalance = IERC4626(_strategy).maxWithdraw(address(this)); + $.strategies[_strategy].status = IEulerAggregationVault.StrategyStatus.Active; + $.strategies[_strategy].allocated = vaultStrategyBalance.toUint120(); $.totalAllocationPoints += strategyCached.allocationPoints; - $.totalAllocated += IERC4626(_strategy).maxWithdraw(address(this)); + $.totalAllocated += vaultStrategyBalance; } } diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 61b92237..2d74e078 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -61,11 +61,13 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { function invariant_gulp() public { eulerAggregationVault.gulp(); - assertEq( - eulerAggregationVault.totalAssetsAllocatable(), - eulerAggregationVault.totalAssetsDeposited() - + (eulerAggregationVault.getAggregationVaultSavingRate()).interestLeft - ); + if (eulerAggregationVault.totalSupply() >= eulerAggregationVault.MIN_SHARES_FOR_GULP()) { + assertEq( + eulerAggregationVault.totalAssetsAllocatable(), + eulerAggregationVault.totalAssetsDeposited() + + (eulerAggregationVault.getAggregationVaultSavingRate()).interestLeft + ); + } } // totalAssetsDeposited should be equal to the totalAssetsAllocatable after SMEAR has passed. @@ -169,13 +171,13 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { assertEq(eulerAggregationVault.getStrategy(address(0)).cap, 0); } - function invariant_votingPower() public view { - address[] memory actorsList = actorUtil.getActors(); + // function invariant_votingPower() public view { + // address[] memory actorsList = actorUtil.getActors(); - for (uint256 i; i < actorsList.length; i++) { - assertEq(eulerAggregationVault.balanceOf(actorsList[i]), eulerAggregationVault.getVotes(actorsList[i])); - } - } + // for (uint256 i; i < actorsList.length; i++) { + // assertEq(eulerAggregationVault.balanceOf(actorsList[i]), eulerAggregationVault.getVotes(actorsList[i])); + // } + // } function _deployOtherStrategies() private { eTSTsecond = IEVault( From 0ab9e1c3537aa903e63e2d867a332d6cc14664a2 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 22 Jul 2024 17:23:17 +0300 Subject: [PATCH 273/316] update --- src/core/EulerAggregationVault.sol | 2 +- src/core/EulerAggregationVaultFactory.sol | 31 ++++++++++++------- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 2 ++ .../EulerAggregationLayerInvariants.t.sol | 12 +++---- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 7148c59e..192e4762 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -32,8 +32,8 @@ import {EventsLib as Events} from "./lib/EventsLib.sol"; /// @dev Do NOT use with rebasing tokens /// @dev inspired by Yearn v3 ❤️ contract EulerAggregationVault is - ERC20VotesUpgradeable, ERC4626Upgradeable, + ERC20VotesUpgradeable, AccessControlEnumerableUpgradeable, Dispatch, IEulerAggregationVault diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index a57da1c2..6cda298c 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -23,7 +23,7 @@ contract EulerAggregationVaultFactory { address public immutable strategyModule; address public immutable rebalanceModule; /// aggregation vault implementation address - address public immutable aggregationVaultImpl; + // address public immutable aggregationVaultImpl; address[] public aggregationVaults; @@ -52,15 +52,15 @@ contract EulerAggregationVaultFactory { strategyModule = Clones.clone(_factoryParams.strategyModuleImpl); rebalanceModule = Clones.clone(_factoryParams.rebalanceModuleImpl); - IEulerAggregationVault.ConstructorParams memory aggregationVaultConstructorParams = IEulerAggregationVault - .ConstructorParams({ - rewardsModule: rewardsModule, - hooksModule: hooksModule, - feeModule: feeModule, - strategyModule: strategyModule, - rebalanceModule: rebalanceModule - }); - aggregationVaultImpl = address(new EulerAggregationVault(aggregationVaultConstructorParams)); + // IEulerAggregationVault.ConstructorParams memory aggregationVaultConstructorParams = IEulerAggregationVault + // .ConstructorParams({ + // rewardsModule: rewardsModule, + // hooksModule: hooksModule, + // feeModule: feeModule, + // strategyModule: strategyModule, + // rebalanceModule: rebalanceModule + // }); + // aggregationVaultImpl = address(new EulerAggregationVault(aggregationVaultConstructorParams)); } /// @notice Deploy a new aggregation vault. @@ -79,7 +79,16 @@ contract EulerAggregationVaultFactory { string memory _symbol, uint256 _initialCashAllocationPoints ) external returns (address) { - address eulerAggregationVault = Clones.clone(aggregationVaultImpl); + // address eulerAggregationVault = Clones.clone(aggregationVaultImpl); + IEulerAggregationVault.ConstructorParams memory aggregationVaultConstructorParams = IEulerAggregationVault + .ConstructorParams({ + rewardsModule: rewardsModule, + hooksModule: hooksModule, + feeModule: feeModule, + strategyModule: strategyModule, + rebalanceModule: rebalanceModule + }); + address eulerAggregationVault = address(new EulerAggregationVault(aggregationVaultConstructorParams)); IEulerAggregationVault.InitParams memory aggregationVaultInitParams = IEulerAggregationVault.InitParams({ aggregationVaultOwner: msg.sender, diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index a52b257a..a169562e 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -44,6 +44,8 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); + assertEq(eulerAggregationVault.numCheckpoints(user1), 0); + assertEq(eulerAggregationVault.balanceOf(user1), eulerAggregationVault.getVotes(user1)); } // rebalance into strategy diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 2d74e078..797bc996 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -171,13 +171,13 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { assertEq(eulerAggregationVault.getStrategy(address(0)).cap, 0); } - // function invariant_votingPower() public view { - // address[] memory actorsList = actorUtil.getActors(); + function invariant_votingPower() public view { + address[] memory actorsList = actorUtil.getActors(); - // for (uint256 i; i < actorsList.length; i++) { - // assertEq(eulerAggregationVault.balanceOf(actorsList[i]), eulerAggregationVault.getVotes(actorsList[i])); - // } - // } + for (uint256 i; i < actorsList.length; i++) { + assertEq(eulerAggregationVault.balanceOf(actorsList[i]), eulerAggregationVault.getVotes(actorsList[i])); + } + } function _deployOtherStrategies() private { eTSTsecond = IEVault( From 45ff916a9030c37ff25b5ab89179377f4d2db360 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:35:00 +0300 Subject: [PATCH 274/316] fix invariant_votingPower --- test/invariant/EulerAggregationLayerInvariants.t.sol | 6 ++++-- .../invariant/handler/EulerAggregationVaultHandler.sol | 8 ++++++++ test/invariant/util/Actor.sol | 10 ++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 797bc996..3b78d1d7 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -31,7 +31,7 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { function setUp() public override { super.setUp(); - actorUtil = new Actor(); + actorUtil = new Actor(address(eulerAggregationVault)); actorUtil.includeActor(manager); actorUtil.includeActor(deployer); actorUtil.includeActor(user1); @@ -76,7 +76,9 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { skip(eulerAggregationVault.INTEREST_SMEAR()); // make sure smear has passed eulerAggregationVault.updateInterestAccrued(); - assertEq(eulerAggregationVault.totalAssets(), eulerAggregationVault.totalAssetsAllocatable()); + if (eulerAggregationVault.totalSupply() >= eulerAggregationVault.MIN_SHARES_FOR_GULP()) { + assertEq(eulerAggregationVault.totalAssets(), eulerAggregationVault.totalAssetsAllocatable()); + } } // total allocation points should be equal to the sum of the allocation points of all strategies. diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index 204d93a6..cddf7e1b 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -269,6 +269,10 @@ contract EulerAggregationVaultHandler is Test { (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); + if (eulerAggVault.totalSupply() == 0) { + uint256 minAssets = eulerAggVault.previewMint(eulerAggVault.MIN_SHARES_FOR_GULP()); + vm.assume(_assets >= minAssets); + } _fillBalance(currentActor, eulerAggVault.asset(), _assets); (currentActor, success, returnData) = actorUtil.initiateExactActorCall( @@ -288,6 +292,10 @@ contract EulerAggregationVaultHandler is Test { (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); + if (eulerAggVault.totalSupply() == 0) { + vm.assume(_shares >= eulerAggVault.MIN_SHARES_FOR_GULP()); + } + uint256 assets = eulerAggVault.previewMint(_shares); _fillBalance(currentActor, eulerAggVault.asset(), assets); diff --git a/test/invariant/util/Actor.sol b/test/invariant/util/Actor.sol index 9f36e4f2..bfc16ef1 100644 --- a/test/invariant/util/Actor.sol +++ b/test/invariant/util/Actor.sol @@ -1,14 +1,24 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol"; import {Test} from "forge-std/Test.sol"; contract Actor is Test { + address eulerAggregationVault; + /// @dev actor[0] will always be a manager address that have access to all EulerAggregationVault roles. address[] public actors; + constructor(address _eulerAggregationVault) { + eulerAggregationVault = _eulerAggregationVault; + } + function includeActor(address _actor) external { actors.push(_actor); + + vm.prank(_actor); + IVotes(eulerAggregationVault).delegate(_actor); } function initiateExactActorCall(uint256 _actorIndex, address _target, bytes memory _calldata) From c3fd0d1621108f20fcf59097939664d976c2fbc2 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:44:19 +0300 Subject: [PATCH 275/316] fix --- test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index a169562e..a52b257a 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -44,8 +44,6 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore + amountToDeposit); assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + amountToDeposit); assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - amountToDeposit); - assertEq(eulerAggregationVault.numCheckpoints(user1), 0); - assertEq(eulerAggregationVault.balanceOf(user1), eulerAggregationVault.getVotes(user1)); } // rebalance into strategy From ba95957b1303026f956155ead4fe55e6c476dba8 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:03:35 +0300 Subject: [PATCH 276/316] chore: update CI --- .github/workflows/echidna.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml index 880c90da..c33085f2 100644 --- a/.github/workflows/echidna.yml +++ b/.github/workflows/echidna.yml @@ -34,5 +34,6 @@ jobs: - name: Run Echidna uses: crytic/echidna-action@v2 with: - files: test/echidna/ - contract: CryticERC4626Harness \ No newline at end of file + files: . + contract: CryticERC4626Harness + crytic-args: --ignore-compile \ No newline at end of file From 668c73ef3537b50245f7ceea0fa8d90cde6cad51 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:09:57 +0300 Subject: [PATCH 277/316] chore: update CI --- .github/workflows/echidna.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml index c33085f2..8497cb7d 100644 --- a/.github/workflows/echidna.yml +++ b/.github/workflows/echidna.yml @@ -35,5 +35,5 @@ jobs: uses: crytic/echidna-action@v2 with: files: . - contract: CryticERC4626Harness + contract: CryticERC4626TestsHarness crytic-args: --ignore-compile \ No newline at end of file From f9fe32a077b7c9ae887672aa87e12d25c35b890f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:13:46 +0300 Subject: [PATCH 278/316] chore: update CI --- .github/workflows/echidna.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml index 8497cb7d..8dd61753 100644 --- a/.github/workflows/echidna.yml +++ b/.github/workflows/echidna.yml @@ -36,4 +36,5 @@ jobs: with: files: . contract: CryticERC4626TestsHarness - crytic-args: --ignore-compile \ No newline at end of file + # crytic-args: --ignore-compile + config: /test/echidna/config/echidna.config.yaml \ No newline at end of file From 2e5299049dcfdbd5ccdf67c75265775d3b2163fd Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:16:41 +0300 Subject: [PATCH 279/316] chore: update CI --- .github/workflows/echidna.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml index 8dd61753..6a10a1fd 100644 --- a/.github/workflows/echidna.yml +++ b/.github/workflows/echidna.yml @@ -37,4 +37,4 @@ jobs: files: . contract: CryticERC4626TestsHarness # crytic-args: --ignore-compile - config: /test/echidna/config/echidna.config.yaml \ No newline at end of file + config: echidna.config.yaml \ No newline at end of file From fce81814286e7c050628dc3340f6ea84f351a09c Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:24:20 +0300 Subject: [PATCH 280/316] chore: update CI --- .github/workflows/echidna.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/echidna.yml b/.github/workflows/echidna.yml index 6a10a1fd..a71069d0 100644 --- a/.github/workflows/echidna.yml +++ b/.github/workflows/echidna.yml @@ -37,4 +37,4 @@ jobs: files: . contract: CryticERC4626TestsHarness # crytic-args: --ignore-compile - config: echidna.config.yaml \ No newline at end of file + config: test/echidna/config/echidna.config.yaml \ No newline at end of file From f53a553d7d68ae15f8a98895b22731165536a09b Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 23 Jul 2024 10:54:59 +0300 Subject: [PATCH 281/316] use default testLimit --- test/echidna/config/echidna.config.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/echidna/config/echidna.config.yaml b/test/echidna/config/echidna.config.yaml index cd3f641b..28c6b774 100644 --- a/test/echidna/config/echidna.config.yaml +++ b/test/echidna/config/echidna.config.yaml @@ -2,9 +2,6 @@ corpusDir: "test/echidna/_corpus/" testMode: assertion -#testLimit is the number of test sequences to run -testLimit: 20000000 - deployer: "0x10000" sender: ["0x10000"] From 6af81ca5c9c8fa0ae325428da30426a3b4961e1f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:31:02 +0300 Subject: [PATCH 282/316] Refactor factory --- src/core/EulerAggregationVaultFactory.sol | 33 ++++++++--------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index 6cda298c..a738f5c2 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -10,8 +10,6 @@ import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) contract EulerAggregationVaultFactory { - error WithdrawalQueueAlreadyWhitelisted(); - error NotWhitelistedWithdrawalQueueImpl(); error InvalidQuery(); /// core dependencies @@ -23,7 +21,7 @@ contract EulerAggregationVaultFactory { address public immutable strategyModule; address public immutable rebalanceModule; /// aggregation vault implementation address - // address public immutable aggregationVaultImpl; + address public immutable aggregationVaultImpl; address[] public aggregationVaults; @@ -52,15 +50,15 @@ contract EulerAggregationVaultFactory { strategyModule = Clones.clone(_factoryParams.strategyModuleImpl); rebalanceModule = Clones.clone(_factoryParams.rebalanceModuleImpl); - // IEulerAggregationVault.ConstructorParams memory aggregationVaultConstructorParams = IEulerAggregationVault - // .ConstructorParams({ - // rewardsModule: rewardsModule, - // hooksModule: hooksModule, - // feeModule: feeModule, - // strategyModule: strategyModule, - // rebalanceModule: rebalanceModule - // }); - // aggregationVaultImpl = address(new EulerAggregationVault(aggregationVaultConstructorParams)); + IEulerAggregationVault.ConstructorParams memory aggregationVaultConstructorParams = IEulerAggregationVault + .ConstructorParams({ + rewardsModule: rewardsModule, + hooksModule: hooksModule, + feeModule: feeModule, + strategyModule: strategyModule, + rebalanceModule: rebalanceModule + }); + aggregationVaultImpl = address(new EulerAggregationVault(aggregationVaultConstructorParams)); } /// @notice Deploy a new aggregation vault. @@ -79,16 +77,7 @@ contract EulerAggregationVaultFactory { string memory _symbol, uint256 _initialCashAllocationPoints ) external returns (address) { - // address eulerAggregationVault = Clones.clone(aggregationVaultImpl); - IEulerAggregationVault.ConstructorParams memory aggregationVaultConstructorParams = IEulerAggregationVault - .ConstructorParams({ - rewardsModule: rewardsModule, - hooksModule: hooksModule, - feeModule: feeModule, - strategyModule: strategyModule, - rebalanceModule: rebalanceModule - }); - address eulerAggregationVault = address(new EulerAggregationVault(aggregationVaultConstructorParams)); + address eulerAggregationVault = Clones.clone(aggregationVaultImpl); IEulerAggregationVault.InitParams memory aggregationVaultInitParams = IEulerAggregationVault.InitParams({ aggregationVaultOwner: msg.sender, From 4bc449c67d655376203e39f7b427561cfa4f6714 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:26:15 +0300 Subject: [PATCH 283/316] refactor --- src/core/EulerAggregationVault.sol | 17 ++++++++--------- src/core/common/Shared.sol | 10 ++++++---- src/core/module/Rebalance.sol | 2 +- ...epositRebalanceHarvestWithdrawE2ETest.t.sol | 12 ++++++------ test/e2e/HarvestRedeemE2ETest.t.sol | 2 +- test/e2e/PerformanceFeeE2ETest.t.sol | 2 +- test/e2e/StrategyCapE2ETest.t.sol | 6 +++--- .../ToggleStrategyEmergencyStatusE2ETest.t.sol | 4 ++-- .../handler/EulerAggregationVaultHandler.sol | 4 ++-- test/unit/GulpTest.t.sol | 2 +- test/unit/HarvestTest.t.sol | 2 +- test/unit/RebalanceTest.t.sol | 18 +++++++++--------- test/unit/RemoveStrategy.t.sol | 2 +- 13 files changed, 42 insertions(+), 41 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 192e4762..ccb224d4 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -187,7 +187,7 @@ contract EulerAggregationVault is /// @dev See {RewardsModule-disableBalanceForwarder}. function disableBalanceForwarder() external override use(rewardsModule) {} - function executeRebalance(address[] calldata _strategies) external override use(rebalanceModule) {} + function rebalance(address[] calldata _strategies) external override use(rebalanceModule) {} /// @notice Harvest all the strategies. Any positive yiled should be gupled by calling gulp() after harvesting. /// @dev This function will loop through the strategies following the withdrawal queue order and harvest all. @@ -247,11 +247,11 @@ contract EulerAggregationVault is if (_receiver == address(this)) revert Errors.CanNotReceiveWithdrawnAsset(); - super._withdraw(_caller, _receiver, _owner, _assets, _shares); - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); $.totalAssetsDeposited -= _assets; + super._withdraw(_caller, _receiver, _owner, _assets, _shares); + _gulp(); } @@ -423,7 +423,7 @@ contract EulerAggregationVault is } /// @dev Loop through stratgies, aggregate positive yield and loss and account for net amount. - /// @dev Loss socialization will be taken out from interest left first, if not enough, sozialize on deposits. + /// @dev Loss socialization will be taken out from interest left first, if not enough, socialize on deposits. function _harvest() internal { // gulp any extra tokens to cover in case of loss socialization _gulp(); @@ -482,13 +482,12 @@ contract EulerAggregationVault is underlyingBalance -= accruedPerformanceFee; yield -= accruedPerformanceFee; } - - $.strategies[_strategy].allocated = uint120(underlyingBalance); } else { loss = strategyAllocatedAmount - underlyingBalance; - - $.strategies[_strategy].allocated = uint120(underlyingBalance); } + + $.strategies[_strategy].allocated = uint120(underlyingBalance); + emit Events.ExecuteHarvest(_strategy, underlyingBalance, strategyAllocatedAmount); return (yield, loss); @@ -545,6 +544,6 @@ contract EulerAggregationVault is function _isCallerWithdrawalQueue() internal view { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - if (_msgSender() != $.withdrawalQueue) revert Errors.NotWithdrawaQueue(); + if (msg.sender != $.withdrawalQueue) revert Errors.NotWithdrawaQueue(); } } diff --git a/src/core/common/Shared.sol b/src/core/common/Shared.sol index 6b7e2f58..2babfbd6 100644 --- a/src/core/common/Shared.sol +++ b/src/core/common/Shared.sol @@ -137,19 +137,21 @@ abstract contract Shared { function _interestAccruedFromCache() internal view returns (uint256) { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + uint40 interestSmearEndCached = $.interestSmearEnd; // If distribution ended, full amount is accrued - if (block.timestamp >= $.interestSmearEnd) { + if (block.timestamp >= interestSmearEndCached) { return $.interestLeft; } + uint40 lastInterestUpdateCached = $.lastInterestUpdate; // If just updated return 0 - if ($.lastInterestUpdate == block.timestamp) { + if (lastInterestUpdateCached == block.timestamp) { return 0; } // Else return what has accrued - uint256 totalDuration = $.interestSmearEnd - $.lastInterestUpdate; - uint256 timePassed = block.timestamp - $.lastInterestUpdate; + uint256 totalDuration = interestSmearEndCached - lastInterestUpdateCached; + uint256 timePassed = block.timestamp - lastInterestUpdateCached; return $.interestLeft * timePassed / totalDuration; } diff --git a/src/core/module/Rebalance.sol b/src/core/module/Rebalance.sol index 260f4864..621b3309 100644 --- a/src/core/module/Rebalance.sol +++ b/src/core/module/Rebalance.sol @@ -21,7 +21,7 @@ abstract contract RebalanceModule is ContextUpgradeable, Shared { /// @notice Rebalance strategies allocation for a specific curated vault. /// @param _strategies Strategies addresses. - function executeRebalance(address[] calldata _strategies) external virtual nonReentrant { + function rebalance(address[] calldata _strategies) external virtual nonReentrant { _gulp(); for (uint256 i; i < _strategies.length; ++i) { diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index a52b257a..45883872 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -59,7 +59,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); @@ -141,7 +141,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); @@ -240,7 +240,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq( eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash @@ -330,7 +330,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); @@ -434,7 +434,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq( eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash @@ -566,7 +566,7 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq( eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash diff --git a/test/e2e/HarvestRedeemE2ETest.t.sol b/test/e2e/HarvestRedeemE2ETest.t.sol index 16469565..5c9ed1ed 100644 --- a/test/e2e/HarvestRedeemE2ETest.t.sol +++ b/test/e2e/HarvestRedeemE2ETest.t.sol @@ -52,7 +52,7 @@ contract HarvestRedeemE2ETest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 68dfe381..4f169ebd 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -86,7 +86,7 @@ contract PerformanceFeeE2ETest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index 53759f1b..010a0b26 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -93,7 +93,7 @@ contract StrategyCapE2ETest is EulerAggregationVaultBase { vm.prank(user1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); @@ -113,7 +113,7 @@ contract StrategyCapE2ETest is EulerAggregationVaultBase { uint256 strategyAllocatedBefore = (eulerAggregationVault.getStrategy(address(eTST))).allocated; strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); vm.stopPrank(); assertEq(strategyAllocatedBefore, (eulerAggregationVault.getStrategy(address(eTST))).allocated); @@ -158,7 +158,7 @@ contract StrategyCapE2ETest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), cap); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), cap); diff --git a/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol index 6ff76615..008c6b27 100644 --- a/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol +++ b/test/e2e/ToggleStrategyEmergencyStatusE2ETest.t.sol @@ -125,7 +125,7 @@ contract ToggleStrategyEmergencyStatusE2ETest is EulerAggregationVaultBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq( eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash @@ -224,7 +224,7 @@ contract ToggleStrategyEmergencyStatusE2ETest is EulerAggregationVaultBase { strategiesToRebalance[0] = address(eTST); strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq( eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index cddf7e1b..f95ad938 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -186,14 +186,14 @@ contract EulerAggregationVaultHandler is Test { assertEq(eulerAggVault.totalAllocationPoints(), ghost_totalAllocationPoints); } - function executeRebalance(uint256 _actorIndexSeed) external { + function rebalance(uint256 _actorIndexSeed) external { (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); (address[] memory strategiesToRebalance, uint256 strategiesCounter) = withdrawalQueue.getWithdrawalQueueArray(); (currentActor, success, returnData) = actorUtil.initiateActorCall( _actorIndexSeed, address(eulerAggVault), - abi.encodeWithSelector(EulerAggregationVault.executeRebalance.selector, strategiesToRebalance) + abi.encodeWithSelector(EulerAggregationVault.rebalance.selector, strategiesToRebalance) ); for (uint256 i; i < strategiesCounter; i++) { diff --git a/test/unit/GulpTest.t.sol b/test/unit/GulpTest.t.sol index 35f146a5..380e358f 100644 --- a/test/unit/GulpTest.t.sol +++ b/test/unit/GulpTest.t.sol @@ -52,7 +52,7 @@ contract GulpTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index b5165d6e..4730d534 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -53,7 +53,7 @@ contract HarvestTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); diff --git a/test/unit/RebalanceTest.t.sol b/test/unit/RebalanceTest.t.sol index aa20bf7f..19a34e46 100644 --- a/test/unit/RebalanceTest.t.sol +++ b/test/unit/RebalanceTest.t.sol @@ -58,7 +58,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); @@ -107,7 +107,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), eTSTMaxDeposit); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), eTSTMaxDeposit); @@ -142,7 +142,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.warp(block.timestamp + 86400); vm.prank(user1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); // create new strategy & add it IEVault eTSTsecondary; @@ -176,7 +176,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); strategiesToRebalance[0] = address(eTSTsecondary); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); // assertEq(eulerAggregationVault.totalAllocated(), eTSTsecondaryMaxDeposit); assertEq( @@ -226,7 +226,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), strategyBefore.allocated); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); @@ -259,7 +259,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; @@ -277,7 +277,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); @@ -313,7 +313,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); // decrease allocation points uint256 newAllocationPoints = 300e18; @@ -339,7 +339,7 @@ contract RebalanceTest is EulerAggregationVaultBase { vm.prank(user1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); // TODO: check this // assertEq(eulerAggregationVault.totalAllocated(), strategyBefore.allocated - eTSTMaxWithdraw); diff --git a/test/unit/RemoveStrategy.t.sol b/test/unit/RemoveStrategy.t.sol index b10a54f9..68b13a0a 100644 --- a/test/unit/RemoveStrategy.t.sol +++ b/test/unit/RemoveStrategy.t.sol @@ -159,7 +159,7 @@ contract RemoveStrategyTest is EulerAggregationVaultBase { vm.prank(user1); address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - eulerAggregationVault.executeRebalance(strategiesToRebalance); + eulerAggregationVault.rebalance(strategiesToRebalance); assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); From 4c93aec8f8985ace40b7a27d32227fbf80c08711 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 23 Jul 2024 22:59:55 +0300 Subject: [PATCH 284/316] fit Strategy struct in one slot --- src/core/EulerAggregationVault.sol | 7 ++- src/core/interface/IEulerAggregationVault.sol | 6 +- src/core/lib/AmountCapLib.sol | 33 +++++++++++ src/core/lib/ErrorsLib.sol | 1 + src/core/module/Rebalance.sol | 5 +- src/core/module/Strategy.sol | 22 +++++-- test/common/EulerAggregationVaultBase.t.sol | 4 ++ test/e2e/StrategyCapE2ETest.t.sol | 57 +++++++++++-------- .../fuzz/AdjustAllocationPointsFuzzTest.t.sol | 2 +- .../EulerAggregationLayerInvariants.t.sol | 5 +- .../handler/EulerAggregationVaultHandler.sol | 15 +++-- 11 files changed, 114 insertions(+), 43 deletions(-) create mode 100644 src/core/lib/AmountCapLib.sol diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index ccb224d4..840b677c 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -22,6 +22,7 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {StorageLib as Storage, AggregationVaultStorage} from "./lib/StorageLib.sol"; +import {AmountCap} from "./lib/AmountCapLib.sol"; import {ErrorsLib as Errors} from "./lib/ErrorsLib.sol"; import {EventsLib as Events} from "./lib/EventsLib.sol"; @@ -75,9 +76,9 @@ contract EulerAggregationVault is $.balanceTracker = _initParams.balanceTracker; $.strategies[address(0)] = IEulerAggregationVault.Strategy({ allocated: 0, - allocationPoints: _initParams.initialCashAllocationPoints.toUint120(), + allocationPoints: _initParams.initialCashAllocationPoints.toUint96(), status: IEulerAggregationVault.StrategyStatus.Active, - cap: 0 + cap: AmountCap.wrap(0) }); $.totalAllocationPoints = _initParams.initialCashAllocationPoints; @@ -163,7 +164,7 @@ contract EulerAggregationVault is function removeStrategy(address _strategy) external override onlyRole(STRATEGY_OPERATOR) use(strategyModule) {} /// @dev See {StrategyModule-setStrategyCap}. - function setStrategyCap(address _strategy, uint256 _cap) external override onlyRole(GUARDIAN) use(strategyModule) {} + function setStrategyCap(address _strategy, uint16 _cap) external override onlyRole(GUARDIAN) use(strategyModule) {} /// @dev See {StrategyModule-adjustAllocationPoints}. function adjustAllocationPoints(address _strategy, uint256 _newPoints) diff --git a/src/core/interface/IEulerAggregationVault.sol b/src/core/interface/IEulerAggregationVault.sol index ca3f5160..03e2e9ba 100644 --- a/src/core/interface/IEulerAggregationVault.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +import {AmountCap} from "../lib/AmountCapLib.sol"; + interface IEulerAggregationVault { /// @dev Struct to pass to constrcutor. struct ConstructorParams { @@ -29,8 +31,8 @@ interface IEulerAggregationVault { /// status: an enum describing the strategy status. Check the enum definition for more details. struct Strategy { uint120 allocated; - uint120 allocationPoints; - uint120 cap; + uint96 allocationPoints; + AmountCap cap; StrategyStatus status; } diff --git a/src/core/lib/AmountCapLib.sol b/src/core/lib/AmountCapLib.sol new file mode 100644 index 00000000..d256ebc7 --- /dev/null +++ b/src/core/lib/AmountCapLib.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +/// @title AmountCapLib +/// @dev This is copied from https://github.com/euler-xyz/euler-vault-kit/blob/20973e1dd2037d26e8dea2f4ab2849e53a77855e/src/EVault/shared/types/AmountCap.sol +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice Library for `AmountCap` custom type +/// @dev AmountCaps are 16-bit decimal floating point values: +/// * The least significant 6 bits are the exponent +/// * The most significant 10 bits are the mantissa, scaled by 100 +/// * The special value of 0 means limit is not set +/// * This is so that uninitialized storage implies no limit +/// * For an actual cap value of 0, use a zero mantissa and non-zero exponent +library AmountCapLib { + function resolve(AmountCap self) internal pure returns (uint256) { + uint256 amountCap = AmountCap.unwrap(self); + + if (amountCap == 0) return type(uint256).max; + + unchecked { + // Cannot overflow because this is less than 2**256: + // 10**(2**6 - 1) * (2**10 - 1) = 1.023e+66 + return 10 ** (amountCap & 63) * (amountCap >> 6) / 100; + } + } + + function toRawUint16(AmountCap self) internal pure returns (uint16) { + return AmountCap.unwrap(self); + } +} + +type AmountCap is uint16; diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index 93545216..21324bdf 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -29,4 +29,5 @@ library ErrorsLib { error CanNotToggleStrategyEmergencyStatus(); error CanNotRemoveStrategyInEmergencyStatus(); error CanNotReceiveWithdrawnAsset(); + error BadStrategyCap(); } diff --git a/src/core/module/Rebalance.sol b/src/core/module/Rebalance.sol index 621b3309..29ae5e26 100644 --- a/src/core/module/Rebalance.sol +++ b/src/core/module/Rebalance.sol @@ -12,12 +12,14 @@ import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgrade import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; +import {AmountCapLib, AmountCap} from "../lib/AmountCapLib.sol"; import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; import {EventsLib as Events} from "../lib/EventsLib.sol"; abstract contract RebalanceModule is ContextUpgradeable, Shared { using SafeERC20 for IERC20; using SafeCast for uint256; + using AmountCapLib for AmountCap; /// @notice Rebalance strategies allocation for a specific curated vault. /// @param _strategies Strategies addresses. @@ -51,7 +53,8 @@ abstract contract RebalanceModule is ContextUpgradeable, Shared { uint256 targetAllocation = totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; - if ((strategyData.cap > 0) && (targetAllocation > strategyData.cap)) targetAllocation = strategyData.cap; + uint120 capAmount = uint120(strategyData.cap.resolve()); + if ((AmountCap.unwrap(strategyData.cap) != 0) && (targetAllocation > capAmount)) targetAllocation = capAmount; uint256 amountToRebalance; bool isDeposit; diff --git a/src/core/module/Strategy.sol b/src/core/module/Strategy.sol index 38a80003..a46c823e 100644 --- a/src/core/module/Strategy.sol +++ b/src/core/module/Strategy.sol @@ -10,6 +10,7 @@ import {Shared} from "../common/Shared.sol"; // libs import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; +import {AmountCapLib, AmountCap} from "../lib/AmountCapLib.sol"; import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; import {EventsLib as Events} from "../lib/EventsLib.sol"; @@ -18,6 +19,10 @@ import {EventsLib as Events} from "../lib/EventsLib.sol"; /// @author Euler Labs (https://www.eulerlabs.com/) abstract contract StrategyModule is Shared { using SafeCast for uint256; + using AmountCapLib for AmountCap; + + // max cap amount, which is the same as the max amount Strategy.allocated can hold. + uint256 public constant MAX_CAP_AMOUNT = type(uint120).max; /// @notice Adjust a certain strategy's allocation points. /// @dev Can only be called by an address that have the `GUARDIAN` role. @@ -35,7 +40,7 @@ abstract contract StrategyModule is Shared { revert Errors.InvalidAllocationPoints(); } - $.strategies[_strategy].allocationPoints = _newPoints.toUint120(); + $.strategies[_strategy].allocationPoints = _newPoints.toUint96(); $.totalAllocationPoints = $.totalAllocationPoints + _newPoints - strategyDataCache.allocationPoints; emit Events.AdjustAllocationPoints(_strategy, strategyDataCache.allocationPoints, _newPoints); @@ -46,7 +51,7 @@ abstract contract StrategyModule is Shared { /// @dev By default, cap is set to 0. /// @param _strategy Strategy address. /// @param _cap Cap amount - function setStrategyCap(address _strategy, uint256 _cap) external virtual nonReentrant { + function setStrategyCap(address _strategy, uint16 _cap) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); if ($.strategies[_strategy].status != IEulerAggregationVault.StrategyStatus.Active) { @@ -57,7 +62,12 @@ abstract contract StrategyModule is Shared { revert Errors.NoCapOnCashReserveStrategy(); } - $.strategies[_strategy].cap = _cap.toUint120(); + AmountCap strategyCap = AmountCap.wrap(_cap); + // The raw uint16 cap amount == 0 is a special value. See comments in AmountCapLib.sol + // Max cap is max amount that can be allocated into strategy (max uint120). + if (_cap != 0 && strategyCap.resolve() > MAX_CAP_AMOUNT) revert Errors.BadStrategyCap(); + + $.strategies[_strategy].cap = strategyCap; emit Events.SetStrategyCap(_strategy, _cap); } @@ -116,9 +126,9 @@ abstract contract StrategyModule is Shared { $.strategies[_strategy] = IEulerAggregationVault.Strategy({ allocated: 0, - allocationPoints: _allocationPoints.toUint120(), + allocationPoints: _allocationPoints.toUint96(), status: IEulerAggregationVault.StrategyStatus.Active, - cap: 0 + cap: AmountCap.wrap(0) }); $.totalAllocationPoints += _allocationPoints; @@ -152,7 +162,7 @@ abstract contract StrategyModule is Shared { $.totalAllocationPoints -= strategyStorage.allocationPoints; strategyStorage.status = IEulerAggregationVault.StrategyStatus.Inactive; strategyStorage.allocationPoints = 0; - strategyStorage.cap = 0; + strategyStorage.cap = AmountCap.wrap(0); // remove from withdrawalQueue IWithdrawalQueue($.withdrawalQueue).removeStrategyFromWithdrawalQueue(_strategy); diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index eb909388..055dd956 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -16,8 +16,12 @@ import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; import {Strategy} from "../../src/core/module/Strategy.sol"; // libs import {ErrorsLib} from "../../src/core/lib/ErrorsLib.sol"; +import {ErrorsLib} from "../../src/core/lib/ErrorsLib.sol"; +import {AmountCapLib as AggAmountCapLib, AmountCap as AggAmountCap} from "../../src/core/lib/AmountCapLib.sol"; contract EulerAggregationVaultBase is EVaultTestBase { + using AggAmountCapLib for AggAmountCap; + uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; address deployer; diff --git a/test/e2e/StrategyCapE2ETest.t.sol b/test/e2e/StrategyCapE2ETest.t.sol index 010a0b26..4b91edac 100644 --- a/test/e2e/StrategyCapE2ETest.t.sol +++ b/test/e2e/StrategyCapE2ETest.t.sol @@ -10,12 +10,16 @@ import { IRMTestDefault, TestERC20, IEulerAggregationVault, - ErrorsLib + ErrorsLib, + AggAmountCapLib, + AggAmountCap } from "../common/EulerAggregationVaultBase.t.sol"; contract StrategyCapE2ETest is EulerAggregationVaultBase { uint256 user1InitialBalance = 100000e18; + using AggAmountCapLib for AggAmountCap; + function setUp() public virtual override { super.setUp(); @@ -26,40 +30,43 @@ contract StrategyCapE2ETest is EulerAggregationVaultBase { } function testSetCap() public { - uint256 cap = 1000000e18; + uint256 cap = 100e18; - assertEq((eulerAggregationVault.getStrategy(address(eTST))).cap, 0); + assertEq(AggAmountCap.unwrap(eulerAggregationVault.getStrategy(address(eTST)).cap), 0); vm.prank(manager); - eulerAggregationVault.setStrategyCap(address(eTST), cap); + // 100e18 cap + eulerAggregationVault.setStrategyCap(address(eTST), 6420); IEulerAggregationVault.Strategy memory strategy = eulerAggregationVault.getStrategy(address(eTST)); - assertEq(strategy.cap, cap); + assertEq(strategy.cap.resolve(), cap); + assertEq(AggAmountCap.unwrap(strategy.cap), 6420); } function testSetCapForInactiveStrategy() public { - uint256 cap = 1000000e18; - vm.prank(manager); vm.expectRevert(ErrorsLib.InactiveStrategy.selector); - eulerAggregationVault.setStrategyCap(address(0x2), cap); + eulerAggregationVault.setStrategyCap(address(0x2), 1); } function testSetCapForCashReserveStrategy() public { - uint256 cap = 1000000e18; - vm.prank(manager); vm.expectRevert(ErrorsLib.NoCapOnCashReserveStrategy.selector); - eulerAggregationVault.setStrategyCap(address(0), cap); + eulerAggregationVault.setStrategyCap(address(0), 1); } function testRebalanceAfterHittingCap() public { address[] memory strategiesToRebalance = new address[](1); - uint256 cap = 3333333333333333333333; + uint120 cappedBalance = 3000000000000000000000; + // 3000000000000000000000 cap + uint16 cap = 19221; vm.prank(manager); eulerAggregationVault.setStrategyCap(address(eTST), cap); + IEulerAggregationVault.Strategy memory strategy = eulerAggregationVault.getStrategy(address(eTST)); + assertEq(strategy.cap.resolve(), cappedBalance); + assertEq(AggAmountCap.unwrap(strategy.cap), cap); uint256 amountToDeposit = 10000e18; @@ -95,11 +102,11 @@ contract StrategyCapE2ETest is EulerAggregationVaultBase { strategiesToRebalance[0] = address(eTST); eulerAggregationVault.rebalance(strategiesToRebalance); - assertEq(eulerAggregationVault.totalAllocated(), expectedStrategyCash); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedStrategyCash); + assertTrue(expectedStrategyCash > cappedBalance); + assertEq(eulerAggregationVault.totalAllocated(), cappedBalance); + assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), cappedBalance); assertEq( - (eulerAggregationVault.getStrategy(address(eTST))).allocated, - strategyBefore.allocated + expectedStrategyCash + (eulerAggregationVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated + cappedBalance ); } @@ -147,11 +154,8 @@ contract StrategyCapE2ETest is EulerAggregationVaultBase { assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), strategyBefore.allocated); - uint256 expectedStrategyCash = eulerAggregationVault.totalAssetsAllocatable() - * strategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); - - // set cap 10% less than target allocation - uint256 cap = expectedStrategyCash * 9e17 / 1e18; + // set cap at around 10% less than target allocation + uint16 cap = 19219; vm.prank(manager); eulerAggregationVault.setStrategyCap(address(eTST), cap); @@ -160,9 +164,14 @@ contract StrategyCapE2ETest is EulerAggregationVaultBase { strategiesToRebalance[0] = address(eTST); eulerAggregationVault.rebalance(strategiesToRebalance); - assertEq(eulerAggregationVault.totalAllocated(), cap); - assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), cap); - assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, strategyBefore.allocated + cap); + assertEq(eulerAggregationVault.totalAllocated(), AggAmountCap.wrap(cap).resolve()); + assertEq( + eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), AggAmountCap.wrap(cap).resolve() + ); + assertEq( + (eulerAggregationVault.getStrategy(address(eTST))).allocated, + strategyBefore.allocated + AggAmountCap.wrap(cap).resolve() + ); } } } diff --git a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol index b9762996..13b89404 100644 --- a/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol +++ b/test/fuzz/AdjustAllocationPointsFuzzTest.t.sol @@ -16,7 +16,7 @@ contract AdjustAllocationsPointsFuzzTest is EulerAggregationVaultBase { } function testFuzzAdjustAllocationPoints(uint256 _newAllocationPoints) public { - _newAllocationPoints = bound(_newAllocationPoints, 1, type(uint120).max); + _newAllocationPoints = bound(_newAllocationPoints, 1, type(uint96).max); uint256 strategyAllocationPoints = (eulerAggregationVault.getStrategy(address(eTST))).allocationPoints; uint256 totalAllocationPointsBefore = eulerAggregationVault.totalAllocationPoints(); diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 3b78d1d7..0fab410d 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -7,7 +7,8 @@ import { IWithdrawalQueue, IEVault, TestERC20, - IEulerAggregationVault + IEulerAggregationVault, + AggAmountCap } from "../common/EulerAggregationVaultBase.t.sol"; import {Actor} from "./util/Actor.sol"; import {Strategy} from "./util/Strategy.sol"; @@ -170,7 +171,7 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { } function invariant_cashReserveStrategyCap() public view { - assertEq(eulerAggregationVault.getStrategy(address(0)).cap, 0); + assertEq(AggAmountCap.unwrap(eulerAggregationVault.getStrategy(address(0)).cap), 0); } function invariant_votingPower() public view { diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index f95ad938..5774abe4 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -13,13 +13,17 @@ import { IEulerAggregationVault, ErrorsLib, IERC4626, - WithdrawalQueue + WithdrawalQueue, + AggAmountCapLib, + AggAmountCap } from "../../common/EulerAggregationVaultBase.t.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {Actor} from "../util/Actor.sol"; import {Strategy} from "../util/Strategy.sol"; contract EulerAggregationVaultHandler is Test { + using AggAmountCapLib for AggAmountCap; + Actor internal actorUtil; Strategy internal strategyUtil; EulerAggregationVault internal eulerAggVault; @@ -101,9 +105,12 @@ contract EulerAggregationVaultHandler is Test { assertEq(strategyAfter.allocationPoints, ghost_allocationPoints[strategyAddr]); } - function setStrategyCap(uint256 _strategyIndexSeed, uint256 _cap) external { + function setStrategyCap(uint256 _strategyIndexSeed, uint16 _cap) external { address strategyAddr = strategyUtil.fetchStrategy(_strategyIndexSeed); + uint256 strategyCapAmount = AggAmountCap.wrap(_cap).resolve(); + vm.assume(strategyCapAmount <= eulerAggVault.MAX_CAP_AMOUNT()); + IEulerAggregationVault.Strategy memory strategyBefore = eulerAggVault.getStrategy(strategyAddr); (currentActor, success, returnData) = actorUtil.initiateExactActorCall( @@ -114,9 +121,9 @@ contract EulerAggregationVaultHandler is Test { IEulerAggregationVault.Strategy memory strategyAfter = eulerAggVault.getStrategy(strategyAddr); if (success) { - assertEq(strategyAfter.cap, _cap); + assertEq(AggAmountCap.unwrap(strategyAfter.cap), _cap); } else { - assertEq(strategyAfter.cap, strategyBefore.cap); + assertEq(AggAmountCap.unwrap(strategyAfter.cap), AggAmountCap.unwrap(strategyBefore.cap)); } } From d8b9144e4ae3a45a812f0ea775c35471684be524 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:05:02 +0300 Subject: [PATCH 285/316] refactor --- src/core/EulerAggregationVault.sol | 3 +-- src/core/interface/IEulerAggregationVault.sol | 4 +--- src/core/lib/StorageLib.sol | 4 ++-- src/core/module/Fee.sol | 5 ----- src/core/module/Hooks.sol | 2 -- src/core/module/Rebalance.sol | 10 ++++------ src/core/module/Rewards.sol | 2 +- src/plugin/WithdrawalQueue.sol | 12 ++++++------ 8 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 840b677c..0cf81a9f 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -278,8 +278,7 @@ contract EulerAggregationVault is AggregationVaultSavingRate memory avsr = AggregationVaultSavingRate({ lastInterestUpdate: $.lastInterestUpdate, interestSmearEnd: $.interestSmearEnd, - interestLeft: $.interestLeft, - locked: $.locked + interestLeft: $.interestLeft }); return avsr; diff --git a/src/core/interface/IEulerAggregationVault.sol b/src/core/interface/IEulerAggregationVault.sol index 03e2e9ba..a7c123c7 100644 --- a/src/core/interface/IEulerAggregationVault.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -41,18 +41,16 @@ interface IEulerAggregationVault { /// lastInterestUpdate: last timestamo where interest was updated. /// interestSmearEnd: timestamp when the smearing of interest end. /// interestLeft: amount of interest left to smear. - /// locked: if locked or not for update. struct AggregationVaultSavingRate { uint40 lastInterestUpdate; uint40 interestSmearEnd; uint168 interestLeft; - uint8 locked; } /// @dev An enum for strategy status. /// An inactive strategy is a strategy that is not added to and recognized by the withdrawal queue. /// An active startegy is a well-functional strategy that is added in the withdrawal queue, can be rebalanced and harvested. - /// A strategy status set as Emeregncy, is when the strategy for some reasons can no longer be withdrawn from or deposited it, + /// A strategy status set as Emeregncy, if when the strategy for some reasons can no longer be withdrawn from or deposited into it, /// this will be used as a circuit-breaker to ensure that the aggregation vault can continue functioning as intended, /// and the only impacted strategy will be the one set as Emergency. enum StrategyStatus { diff --git a/src/core/lib/StorageLib.sol b/src/core/lib/StorageLib.sol index 9e34d645..229a8dbd 100644 --- a/src/core/lib/StorageLib.sol +++ b/src/core/lib/StorageLib.sol @@ -18,9 +18,9 @@ struct AggregationVaultStorage { address feeRecipient; /// WithdrawalQueue plugin address address withdrawalQueue; - /// Mapping between strategy address and it's allocation config + /// Mapping between a strategy address and it's allocation config mapping(address => IEulerAggregationVault.Strategy) strategies; - /// lastInterestUpdate: last timestamo where interest was updated. + /// lastInterestUpdate: last timestamp where interest was updated. uint40 lastInterestUpdate; /// interestSmearEnd: timestamp when the smearing of interest end. uint40 interestSmearEnd; diff --git a/src/core/module/Fee.sol b/src/core/module/Fee.sol index f76be6e2..3f1c3b73 100644 --- a/src/core/module/Fee.sol +++ b/src/core/module/Fee.sol @@ -1,11 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -// interfaces -import {IBalanceForwarder} from "../interface/IBalanceForwarder.sol"; -import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; -import {IRewardStreams} from "reward-streams/interfaces/IRewardStreams.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; // libs import {StorageLib, AggregationVaultStorage} from "../lib/StorageLib.sol"; import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; diff --git a/src/core/module/Hooks.sol b/src/core/module/Hooks.sol index 45d0f1a8..0f20379a 100644 --- a/src/core/module/Hooks.sol +++ b/src/core/module/Hooks.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -// interfaces -import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; // contracts import {Shared} from "../common/Shared.sol"; // libs diff --git a/src/core/module/Rebalance.sol b/src/core/module/Rebalance.sol index 29ae5e26..62ea9950 100644 --- a/src/core/module/Rebalance.sol +++ b/src/core/module/Rebalance.sol @@ -66,8 +66,6 @@ abstract contract RebalanceModule is ContextUpgradeable, Shared { if (amountToRebalance > maxWithdraw) { amountToRebalance = maxWithdraw; } - - isDeposit = false; } else if (strategyData.allocated < targetAllocation) { // Deposit uint256 targetCash = @@ -87,13 +85,13 @@ abstract contract RebalanceModule is ContextUpgradeable, Shared { amountToRebalance = maxDeposit; } - if (amountToRebalance == 0) { - return; // No cash to deposit - } - isDeposit = true; } + if (amountToRebalance == 0) { + return; + } + if (isDeposit) { // Do required approval (safely) and deposit IERC20(IERC4626(address(this)).asset()).safeIncreaseAllowance(_strategy, amountToRebalance); diff --git a/src/core/module/Rewards.sol b/src/core/module/Rewards.sol index 53a862db..19e3b387 100644 --- a/src/core/module/Rewards.sol +++ b/src/core/module/Rewards.sol @@ -25,7 +25,7 @@ abstract contract RewardsModule is IBalanceForwarder, Shared { function optInStrategyRewards(address _strategy) external virtual nonReentrant { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - if ($.strategies[_strategy].status == IEulerAggregationVault.StrategyStatus.Inactive) { + if ($.strategies[_strategy].status != IEulerAggregationVault.StrategyStatus.Active) { revert Errors.InactiveStrategy(); } diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index a4afe6f6..7dce63f0 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -60,7 +60,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue /// @notice Remove a strategy from withdrawal queue array. /// @dev Can only be called by the aggregation vault's address. - /// @param _strategy Strategy address to add. + /// @param _strategy Strategy address to remove. function removeStrategyFromWithdrawalQueue(address _strategy) external { _isCallerAggregationVault(); @@ -168,12 +168,12 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); uint256 withdrawalQueueLengthCached = $.withdrawalQueue.length; - address[] memory withdrawalQueueMem = new address[](withdrawalQueueLengthCached); - for (uint256 i; i < withdrawalQueueLengthCached; ++i) { - withdrawalQueueMem[i] = $.withdrawalQueue[i]; - } + // address[] memory withdrawalQueueMem = new address[](withdrawalQueueLengthCached); + // for (uint256 i; i < withdrawalQueueLengthCached; ++i) { + // withdrawalQueueMem[i] = $.withdrawalQueue[i]; + // } - return (withdrawalQueueMem, withdrawalQueueLengthCached); + return ($.withdrawalQueue, withdrawalQueueLengthCached); } /// @notice Return the withdrawal queue length. From 50528790af47a94bd883172d7c5b02ff15c61648 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 24 Jul 2024 12:33:38 +0300 Subject: [PATCH 286/316] add sizes check to CI --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c633cfdb..4afd897b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: version: nightly - name: Run foundry build - run: forge build --force --skip test + run: forge build --force --skip test --sizes - name: Run foundry fmt check run: forge fmt --check From 55073589f7b4a1f0240de84e3fce389d42060919 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 24 Jul 2024 12:44:16 +0300 Subject: [PATCH 287/316] clean --- src/plugin/WithdrawalQueue.sol | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol index 7dce63f0..8276afd4 100644 --- a/src/plugin/WithdrawalQueue.sol +++ b/src/plugin/WithdrawalQueue.sol @@ -168,11 +168,6 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); uint256 withdrawalQueueLengthCached = $.withdrawalQueue.length; - // address[] memory withdrawalQueueMem = new address[](withdrawalQueueLengthCached); - // for (uint256 i; i < withdrawalQueueLengthCached; ++i) { - // withdrawalQueueMem[i] = $.withdrawalQueue[i]; - // } - return ($.withdrawalQueue, withdrawalQueueLengthCached); } From d581e6791e53268a03ac22d7d77bae5295474108 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:13:47 +0300 Subject: [PATCH 288/316] Remove plugin --- src/core/Dispatch.sol | 15 +- src/core/EulerAggregationVault.sol | 140 ++++++------- src/core/EulerAggregationVaultFactory.sol | 14 +- src/core/interface/IEulerAggregationVault.sol | 10 +- src/core/interface/IWithdrawalQueue.sol | 20 -- src/core/lib/ErrorsLib.sol | 3 + src/core/lib/StorageLib.sol | 8 +- src/core/module/Strategy.sol | 13 +- src/core/module/WithdrawalQueue.sol | 52 +++++ src/plugin/WithdrawalQueue.sol | 196 ------------------ test/A16zPropertyTests.t.sol | 14 +- test/common/EulerAggregationVaultBase.t.sol | 59 ++---- test/e2e/BalanceForwarderE2ETest.t.sol | 14 +- test/echidna/CryticERC4626TestsHarness.t.sol | 14 +- .../EulerAggregationLayerInvariants.t.sol | 39 ++-- .../handler/EulerAggregationVaultHandler.sol | 27 ++- .../handler/WithdrawalQueueHandler.sol | 52 ----- test/unit/HarvestTest.t.sol | 7 - test/unit/ReorderWithdrawalQueueTest.t.sol | 13 +- 19 files changed, 227 insertions(+), 483 deletions(-) delete mode 100644 src/core/interface/IWithdrawalQueue.sol create mode 100644 src/core/module/WithdrawalQueue.sol delete mode 100644 src/plugin/WithdrawalQueue.sol delete mode 100644 test/invariant/handler/WithdrawalQueueHandler.sol diff --git a/src/core/Dispatch.sol b/src/core/Dispatch.sol index 2f9bc1f9..96ea109f 100644 --- a/src/core/Dispatch.sol +++ b/src/core/Dispatch.sol @@ -7,18 +7,27 @@ import {RewardsModule} from "./module/Rewards.sol"; import {StrategyModule} from "./module/Strategy.sol"; import {FeeModule} from "./module/Fee.sol"; import {RebalanceModule} from "./module/Rebalance.sol"; +import {WithdrawalQueue} from "./module/WithdrawalQueue.sol"; /// @title Dispatch contract /// @custom:security-contact security@euler.xyz /// @author Euler Labs (https://www.eulerlabs.com/) /// @dev This contract implement the modifier to use for forwarding calls to a specific module using delegateCall. /// @dev Copied from https://github.com/euler-xyz/euler-vault-kit/blob/55d1a1fd7d572372f1c8b9f58aba0604bda3ca4f/src/EVault/Dispatch.sol. -abstract contract Dispatch is RewardsModule, HooksModule, FeeModule, StrategyModule, RebalanceModule { +abstract contract Dispatch is + RewardsModule, + HooksModule, + FeeModule, + StrategyModule, + RebalanceModule, + WithdrawalQueue +{ address public immutable rewardsModule; address public immutable hooksModule; address public immutable feeModule; address public immutable strategyModule; address public immutable rebalanceModule; + address public immutable withdrawalQueueModule; /// @dev Constructor. /// @param _rewardsModule Address of Rewards module. @@ -31,13 +40,15 @@ abstract contract Dispatch is RewardsModule, HooksModule, FeeModule, StrategyMod address _hooksModule, address _feeModule, address _strategyModule, - address _rebalanceModule + address _rebalanceModule, + address _withdrawalQueueModule ) { rewardsModule = _rewardsModule; hooksModule = _hooksModule; feeModule = _feeModule; strategyModule = _strategyModule; rebalanceModule = _rebalanceModule; + withdrawalQueueModule = _withdrawalQueueModule; } // Modifier proxies the function call to a module and low-level returns the result diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 0cf81a9f..13bafe56 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -6,7 +6,6 @@ import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; import {IEulerAggregationVault} from "./interface/IEulerAggregationVault.sol"; -import {IWithdrawalQueue} from "./interface/IWithdrawalQueue.sol"; // contracts import {Dispatch} from "./Dispatch.sol"; import { @@ -57,7 +56,8 @@ contract EulerAggregationVault is _constructorParams.hooksModule, _constructorParams.feeModule, _constructorParams.strategyModule, - _constructorParams.rebalanceModule + _constructorParams.rebalanceModule, + _constructorParams.withdrawalQueueModule ) {} @@ -72,7 +72,6 @@ contract EulerAggregationVault is AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); $.locked = REENTRANCYLOCK__UNLOCKED; - $.withdrawalQueue = _initParams.withdrawalQueuePlugin; $.balanceTracker = _initParams.balanceTracker; $.strategies[address(0)] = IEulerAggregationVault.Strategy({ allocated: 0, @@ -89,8 +88,6 @@ contract EulerAggregationVault is _setRoleAdmin(GUARDIAN, GUARDIAN_ADMIN); _setRoleAdmin(STRATEGY_OPERATOR, STRATEGY_OPERATOR_ADMIN); _setRoleAdmin(AGGREGATION_VAULT_MANAGER, AGGREGATION_VAULT_MANAGER_ADMIN); - - IWithdrawalQueue(_initParams.withdrawalQueuePlugin).init(_initParams.aggregationVaultOwner); } /// @dev See {FeeModule-setFeeRecipient}. @@ -190,6 +187,14 @@ contract EulerAggregationVault is function rebalance(address[] calldata _strategies) external override use(rebalanceModule) {} + function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) public override { + super.reorderWithdrawalQueue(_index1, _index2); + } + + function withdrawalQueue() public view override returns (address[] memory) { + return super.withdrawalQueue(); + } + /// @notice Harvest all the strategies. Any positive yiled should be gupled by calling gulp() after harvesting. /// @dev This function will loop through the strategies following the withdrawal queue order and harvest all. /// @dev Harvest yield and losses will be aggregated and only net yield/loss will be accounted. @@ -209,53 +214,6 @@ contract EulerAggregationVault is _gulp(); } - /// @notice Execute a withdraw from a strategy. - /// @dev Can only be called from the WithdrawalQueue contract. - /// @param _strategy Strategy's address. - /// @param _withdrawAmount Amount to withdraw. - function executeStrategyWithdraw(address _strategy, uint256 _withdrawAmount) external returns (uint256) { - _isCallerWithdrawalQueue(); - - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - - if ($.strategies[_strategy].status != IEulerAggregationVault.StrategyStatus.Active) return 0; - - // Update allocated assets - $.strategies[_strategy].allocated -= uint120(_withdrawAmount); - $.totalAllocated -= _withdrawAmount; - - // Do actual withdraw from strategy - IERC4626(_strategy).withdraw(_withdrawAmount, address(this), address(this)); - - return _withdrawAmount; - } - - /// @notice Execute a withdraw from the AggregationVault - /// @dev This function should be called and can only be called by the WithdrawalQueue. - /// @param _caller Withdraw call initiator. - /// @param _receiver Receiver of the withdrawn asset. - /// @param _owner Owner of shares to withdraw against. - /// @param _assets Amount of asset to withdraw. - /// @param _shares Amount of shares to withdraw against. - function executeAggregationVaultWithdraw( - address _caller, - address _receiver, - address _owner, - uint256 _assets, - uint256 _shares - ) external { - _isCallerWithdrawalQueue(); - - if (_receiver == address(this)) revert Errors.CanNotReceiveWithdrawnAsset(); - - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - $.totalAssetsDeposited -= _assets; - - super._withdraw(_caller, _receiver, _owner, _assets, _shares); - - _gulp(); - } - /// @notice Get strategy params. /// @param _strategy strategy's address /// @return Strategy struct @@ -308,14 +266,6 @@ contract EulerAggregationVault is return $.totalAssetsDeposited; } - /// @notice Get the WithdrawalQueue plugin address. - /// @return address Withdrawal queue address. - function withdrawalQueue() external view returns (address) { - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - - return $.withdrawalQueue; - } - /// @notice Get the performance fee config. /// @return adddress Fee recipient. /// @return uint256 Fee percentage. @@ -325,6 +275,23 @@ contract EulerAggregationVault is return ($.feeRecipient, $.performanceFee); } + /// @notice Get strategy address from withdrawal queue by index. + /// @param _index Index to fetch. + /// @return address Strategy address. + function getWithdrawalQueueAtIndex(uint256 _index) external view returns (address) { + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + + return $.withdrawalQueue[_index]; + } + + /// @notice Get the withdrawal queue array and it's length. + /// @return withdrawalQueueMem The withdrawal queue array in memory. + function getWithdrawalQueueArray() external view returns (address[] memory) { + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + + return $.withdrawalQueue; + } + /// @dev See {IERC4626-deposit}. /// @dev Call DEPOSIT hook if enabled. function deposit(uint256 _assets, address _receiver) public override nonReentrant returns (uint256) { @@ -417,9 +384,42 @@ contract EulerAggregationVault is AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); uint256 assetsRetrieved = IERC20(asset()).balanceOf(address(this)); - IWithdrawalQueue($.withdrawalQueue).callWithdrawalQueue( - _caller, _receiver, _owner, _assets, _shares, assetsRetrieved - ); + if (assetsRetrieved < _assets) { + uint256 numStrategies = $.withdrawalQueue.length; + for (uint256 i; i < numStrategies; ++i) { + IERC4626 strategy = IERC4626($.withdrawalQueue[i]); + + if ($.strategies[address(strategy)].status != IEulerAggregationVault.StrategyStatus.Active) continue; + + uint256 underlyingBalance = strategy.maxWithdraw(address(this)); + uint256 desiredAssets = _assets - assetsRetrieved; + uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; + + // Update allocated assets + $.strategies[address(strategy)].allocated -= uint120(withdrawAmount); + $.totalAllocated -= withdrawAmount; + + // Do actual withdraw from strategy + IERC4626(address(strategy)).withdraw(withdrawAmount, address(this), address(this)); + + // update _availableAssets + assetsRetrieved += withdrawAmount; + + if (assetsRetrieved >= _assets) { + break; + } + } + } + + if (assetsRetrieved < _assets) { + revert Errors.NotEnoughAssets(); + } + + $.totalAssetsDeposited -= _assets; + + super._withdraw(_caller, _receiver, _owner, _assets, _shares); + + _gulp(); } /// @dev Loop through stratgies, aggregate positive yield and loss and account for net amount. @@ -430,13 +430,10 @@ contract EulerAggregationVault is AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - (address[] memory withdrawalQueueArray, uint256 length) = - IWithdrawalQueue($.withdrawalQueue).getWithdrawalQueueArray(); - uint256 totalYield; uint256 totalLoss; - for (uint256 i; i < length; ++i) { - (uint256 yield, uint256 loss) = _executeHarvest(withdrawalQueueArray[i]); + for (uint256 i; i < $.withdrawalQueue.length; ++i) { + (uint256 yield, uint256 loss) = _executeHarvest($.withdrawalQueue[i]); totalYield += yield; totalLoss += loss; @@ -539,11 +536,4 @@ contract EulerAggregationVault is balanceTracker.balanceTrackerHook(to, super.balanceOf(to), false); } } - - /// @dev Check if caller is WithdrawalQueue address, if not revert. - function _isCallerWithdrawalQueue() internal view { - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - - if (msg.sender != $.withdrawalQueue) revert Errors.NotWithdrawaQueue(); - } } diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index a738f5c2..836404c1 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -20,6 +20,7 @@ contract EulerAggregationVaultFactory { address public immutable feeModule; address public immutable strategyModule; address public immutable rebalanceModule; + address public immutable withdrawalQueueModule; /// aggregation vault implementation address address public immutable aggregationVaultImpl; @@ -34,11 +35,10 @@ contract EulerAggregationVaultFactory { address feeModuleImpl; address strategyModuleImpl; address rebalanceModuleImpl; + address withdrawalQueueModuleImpl; } - event DeployEulerAggregationVault( - address indexed _owner, address _aggregationVault, address indexed _withdrawalQueueImpl, address indexed _asset - ); + event DeployEulerAggregationVault(address indexed _owner, address _aggregationVault, address indexed _asset); /// @notice Constructor. /// @param _factoryParams FactoryParams struct. @@ -49,6 +49,7 @@ contract EulerAggregationVaultFactory { feeModule = Clones.clone(_factoryParams.feeModuleImpl); strategyModule = Clones.clone(_factoryParams.strategyModuleImpl); rebalanceModule = Clones.clone(_factoryParams.rebalanceModuleImpl); + withdrawalQueueModule = Clones.clone(_factoryParams.withdrawalQueueModuleImpl); IEulerAggregationVault.ConstructorParams memory aggregationVaultConstructorParams = IEulerAggregationVault .ConstructorParams({ @@ -56,7 +57,8 @@ contract EulerAggregationVaultFactory { hooksModule: hooksModule, feeModule: feeModule, strategyModule: strategyModule, - rebalanceModule: rebalanceModule + rebalanceModule: rebalanceModule, + withdrawalQueueModule: withdrawalQueueModule }); aggregationVaultImpl = address(new EulerAggregationVault(aggregationVaultConstructorParams)); } @@ -71,7 +73,6 @@ contract EulerAggregationVaultFactory { /// @param _initialCashAllocationPoints The amount of points to initally allocate for cash reserve. /// @return eulerAggregationVault The address of the new deployed aggregation vault. function deployEulerAggregationVault( - address _withdrawalQueueImpl, address _asset, string memory _name, string memory _symbol, @@ -82,7 +83,6 @@ contract EulerAggregationVaultFactory { IEulerAggregationVault.InitParams memory aggregationVaultInitParams = IEulerAggregationVault.InitParams({ aggregationVaultOwner: msg.sender, asset: _asset, - withdrawalQueuePlugin: Clones.clone(_withdrawalQueueImpl), balanceTracker: balanceTracker, name: _name, symbol: _symbol, @@ -92,7 +92,7 @@ contract EulerAggregationVaultFactory { aggregationVaults.push(address(eulerAggregationVault)); - emit DeployEulerAggregationVault(msg.sender, address(eulerAggregationVault), _withdrawalQueueImpl, _asset); + emit DeployEulerAggregationVault(msg.sender, address(eulerAggregationVault), _asset); return eulerAggregationVault; } diff --git a/src/core/interface/IEulerAggregationVault.sol b/src/core/interface/IEulerAggregationVault.sol index a7c123c7..724228c4 100644 --- a/src/core/interface/IEulerAggregationVault.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -11,13 +11,13 @@ interface IEulerAggregationVault { address feeModule; address strategyModule; address rebalanceModule; + address withdrawalQueueModule; } /// @dev Struct to pass init() call params. struct InitParams { address aggregationVaultOwner; address asset; - address withdrawalQueuePlugin; address balanceTracker; string name; string symbol; @@ -62,14 +62,6 @@ interface IEulerAggregationVault { function init(InitParams calldata _initParams) external; function gulp() external; function harvest() external; - function executeStrategyWithdraw(address _strategy, uint256 _withdrawAmount) external returns (uint256); - function executeAggregationVaultWithdraw( - address caller, - address receiver, - address owner, - uint256 assets, - uint256 shares - ) external; function getStrategy(address _strategy) external view returns (Strategy memory); function totalAllocationPoints() external view returns (uint256); function totalAllocated() external view returns (uint256); diff --git a/src/core/interface/IWithdrawalQueue.sol b/src/core/interface/IWithdrawalQueue.sol deleted file mode 100644 index cf554c26..00000000 --- a/src/core/interface/IWithdrawalQueue.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -interface IWithdrawalQueue { - function init(address _owner) external; - function addStrategyToWithdrawalQueue(address _strategy) external; - function removeStrategyFromWithdrawalQueue(address _strategy) external; - function callWithdrawalQueue( - address caller, - address receiver, - address owner, - uint256 assets, - uint256 shares, - uint256 availableAssets - ) external; - function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external; - - function withdrawalQueueLength() external view returns (uint256); - function getWithdrawalQueueArray() external view returns (address[] memory, uint256); -} diff --git a/src/core/lib/ErrorsLib.sol b/src/core/lib/ErrorsLib.sol index 21324bdf..f401b2df 100644 --- a/src/core/lib/ErrorsLib.sol +++ b/src/core/lib/ErrorsLib.sol @@ -30,4 +30,7 @@ library ErrorsLib { error CanNotRemoveStrategyInEmergencyStatus(); error CanNotReceiveWithdrawnAsset(); error BadStrategyCap(); + error OutOfBounds(); + error SameIndexes(); + error NotEnoughAssets(); } diff --git a/src/core/lib/StorageLib.sol b/src/core/lib/StorageLib.sol index 229a8dbd..d5fd433c 100644 --- a/src/core/lib/StorageLib.sol +++ b/src/core/lib/StorageLib.sol @@ -16,10 +16,14 @@ struct AggregationVaultStorage { uint256 performanceFee; /// fee recipient address address feeRecipient; - /// WithdrawalQueue plugin address - address withdrawalQueue; + // /// WithdrawalQueue plugin address + // address withdrawalQueue; /// Mapping between a strategy address and it's allocation config mapping(address => IEulerAggregationVault.Strategy) strategies; + /// @dev An array of strategy addresses to withdraw from + address[] withdrawalQueue; + + /// lastInterestUpdate: last timestamp where interest was updated. uint40 lastInterestUpdate; /// interestSmearEnd: timestamp when the smearing of interest end. diff --git a/src/core/module/Strategy.sol b/src/core/module/Strategy.sol index a46c823e..3a47c455 100644 --- a/src/core/module/Strategy.sol +++ b/src/core/module/Strategy.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; // interfaces import {IERC4626} from "@openzeppelin-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; -import {IWithdrawalQueue} from "../interface/IWithdrawalQueue.sol"; import {IEulerAggregationVault} from "../interface/IEulerAggregationVault.sol"; // contracts import {Shared} from "../common/Shared.sol"; @@ -132,7 +131,7 @@ abstract contract StrategyModule is Shared { }); $.totalAllocationPoints += _allocationPoints; - IWithdrawalQueue($.withdrawalQueue).addStrategyToWithdrawalQueue(_strategy); + $.withdrawalQueue.push(_strategy); emit Events.AddStrategy(_strategy, _allocationPoints); } @@ -165,7 +164,15 @@ abstract contract StrategyModule is Shared { strategyStorage.cap = AmountCap.wrap(0); // remove from withdrawalQueue - IWithdrawalQueue($.withdrawalQueue).removeStrategyFromWithdrawalQueue(_strategy); + uint256 lastStrategyIndex = $.withdrawalQueue.length - 1; + for (uint256 i = 0; i < lastStrategyIndex; ++i) { + if ($.withdrawalQueue[i] == _strategy) { + $.withdrawalQueue[i] = $.withdrawalQueue[lastStrategyIndex]; + + break; + } + } + $.withdrawalQueue.pop(); emit Events.RemoveStrategy(_strategy); } diff --git a/src/core/module/WithdrawalQueue.sol b/src/core/module/WithdrawalQueue.sol new file mode 100644 index 00000000..1ba994e7 --- /dev/null +++ b/src/core/module/WithdrawalQueue.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +// interfaces +import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +import {IEulerAggregationVault} from "../interface/IEulerAggregationVault.sol"; +// contracts +import {AccessControlEnumerableUpgradeable} from + "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; +import {Shared} from "../common/Shared.sol"; +// libs +import {StorageLib as Storage, AggregationVaultStorage} from "../lib/StorageLib.sol"; +import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; + +abstract contract WithdrawalQueueModule is Shared { + // bytes32 public constant WITHDRAW_QUEUE_MANAGER = keccak256("WITHDRAW_QUEUE_MANAGER"); + // bytes32 public constant WITHDRAW_QUEUE_MANAGER_ADMIN = keccak256("WITHDRAW_QUEUE_MANAGER_ADMIN"); + + event ReorderWithdrawalQueue(uint8 index1, uint8 index2); + + /// @notice Swap two strategies indexes in the withdrawal queue. + /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER role. + /// @param _index1 index of first strategy. + /// @param _index2 index of second strategy. + function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) public virtual nonReentrant { + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + + uint256 length = $.withdrawalQueue.length; + if (_index1 >= length || _index2 >= length) { + revert Errors.OutOfBounds(); + } + + if (_index1 == _index2) { + revert Errors.SameIndexes(); + } + + ($.withdrawalQueue[_index1], $.withdrawalQueue[_index2]) = + ($.withdrawalQueue[_index2], $.withdrawalQueue[_index1]); + + emit ReorderWithdrawalQueue(_index1, _index2); + } + + /// @notice Return the withdrawal queue length. + /// @return uint256 length. + function withdrawalQueue() public view virtual returns (address[] memory) { + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + + return $.withdrawalQueue; + } +} + +contract WithdrawalQueue is WithdrawalQueueModule {} diff --git a/src/plugin/WithdrawalQueue.sol b/src/plugin/WithdrawalQueue.sol deleted file mode 100644 index 8276afd4..00000000 --- a/src/plugin/WithdrawalQueue.sol +++ /dev/null @@ -1,196 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -// interfaces -import {IERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; -import {IEulerAggregationVault} from "../core/interface/IEulerAggregationVault.sol"; -import {IWithdrawalQueue} from "../core/interface/IWithdrawalQueue.sol"; -// contracts -import {AccessControlEnumerableUpgradeable} from - "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; - -/// @title WithdrawalQueue plugin -/// @custom:security-contact security@euler.xyz -/// @author Euler Labs (https://www.eulerlabs.com/) -/// @notice This contract manage the withdrawalQueue aray(add/remove strategy to the queue, re-order queue). -/// Also it handles finishing the withdraw execution flow through the `callWithdrawalQueue()` function -/// that will be called by the EulerAggregationVault. -contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue { - error OutOfBounds(); - error SameIndexes(); - error NotEnoughAssets(); - error NotAuthorized(); - - bytes32 public constant WITHDRAW_QUEUE_MANAGER = keccak256("WITHDRAW_QUEUE_MANAGER"); - bytes32 public constant WITHDRAW_QUEUE_MANAGER_ADMIN = keccak256("WITHDRAW_QUEUE_MANAGER_ADMIN"); - - struct WithdrawalQueueStorage { - address eulerAggregationVault; - /// @dev An array of strategy addresses to withdraw from - address[] withdrawalQueue; - } - - // keccak256(abi.encode(uint256(keccak256("euler_aggregation_vault.storage.WithdrawalQueue")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant WithdrawalQueueStorageLocation = - 0x8522ce6e5838588854909d348b0c9f7932eae519636e8e48e91e9b2639174600; - - event ReorderWithdrawalQueue(uint8 index1, uint8 index2); - - /// @notice Initialize WithdrawalQueue. - /// @param _owner Aggregation vault owner. - function init(address _owner) external initializer { - WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - $.eulerAggregationVault = msg.sender; - - // Setup DEFAULT_ADMIN - _grantRole(DEFAULT_ADMIN_ROLE, _owner); - _setRoleAdmin(WITHDRAW_QUEUE_MANAGER, WITHDRAW_QUEUE_MANAGER_ADMIN); - } - - /// @notice Add a strategy to withdrawal queue array. - /// @dev Can only be called by the aggregation vault's address. - /// @param _strategy Strategy address to add - function addStrategyToWithdrawalQueue(address _strategy) external { - _isCallerAggregationVault(); - - WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - - $.withdrawalQueue.push(_strategy); - } - - /// @notice Remove a strategy from withdrawal queue array. - /// @dev Can only be called by the aggregation vault's address. - /// @param _strategy Strategy address to remove. - function removeStrategyFromWithdrawalQueue(address _strategy) external { - _isCallerAggregationVault(); - - WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - - uint256 lastStrategyIndex = $.withdrawalQueue.length - 1; - - for (uint256 i = 0; i < lastStrategyIndex; ++i) { - if ($.withdrawalQueue[i] == _strategy) { - $.withdrawalQueue[i] = $.withdrawalQueue[lastStrategyIndex]; - - break; - } - } - - $.withdrawalQueue.pop(); - } - - /// @notice Swap two strategies indexes in the withdrawal queue. - /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER role. - /// @param _index1 index of first strategy. - /// @param _index2 index of second strategy. - function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external onlyRole(WITHDRAW_QUEUE_MANAGER) { - WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - - uint256 length = $.withdrawalQueue.length; - if (_index1 >= length || _index2 >= length) { - revert OutOfBounds(); - } - - if (_index1 == _index2) { - revert SameIndexes(); - } - - ($.withdrawalQueue[_index1], $.withdrawalQueue[_index2]) = - ($.withdrawalQueue[_index2], $.withdrawalQueue[_index1]); - - emit ReorderWithdrawalQueue(_index1, _index2); - } - - /// @notice Execute the withdraw initiated in the aggregation vault. - /// @dev Can only be called by the aggregation vault's address. - /// @param _caller Initiator's address of withdraw. - /// @param _receiver Withdraw receiver address. - /// @param _owner Shares's owner to burn. - /// @param _assets Amount of asset to withdraw. - /// @param _shares Amount of shares to burn. - /// @param _availableAssets Amount of available asset in aggregation vault's cash reserve. - function callWithdrawalQueue( - address _caller, - address _receiver, - address _owner, - uint256 _assets, - uint256 _shares, - uint256 _availableAssets - ) external { - _isCallerAggregationVault(); - - WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - address eulerAggregationVaultCached = $.eulerAggregationVault; - - if (_availableAssets < _assets) { - uint256 numStrategies = $.withdrawalQueue.length; - for (uint256 i; i < numStrategies; ++i) { - IERC4626 strategy = IERC4626($.withdrawalQueue[i]); - - uint256 underlyingBalance = strategy.maxWithdraw(eulerAggregationVaultCached); - uint256 desiredAssets = _assets - _availableAssets; - uint256 withdrawAmount = (underlyingBalance > desiredAssets) ? desiredAssets : underlyingBalance; - - // update _availableAssets - _availableAssets += IEulerAggregationVault(eulerAggregationVaultCached).executeStrategyWithdraw( - address(strategy), withdrawAmount - ); - - if (_availableAssets >= _assets) { - break; - } - } - } - - //TODO: is this even possible now? - if (_availableAssets < _assets) { - revert NotEnoughAssets(); - } - - IEulerAggregationVault(eulerAggregationVaultCached).executeAggregationVaultWithdraw( - _caller, _receiver, _owner, _assets, _shares - ); - } - - /// @notice Get strategy address from withdrawal queue by index. - /// @param _index Index to fetch. - /// @return address Strategy address. - function getWithdrawalQueueAtIndex(uint256 _index) external view returns (address) { - WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - - return $.withdrawalQueue[_index]; - } - - /// @notice Get the withdrawal queue array and it's length. - /// @return withdrawalQueueMem The withdrawal queue array in memory. - /// @return withdrawalQueueLengthCached An uint256 which is the length of the array. - function getWithdrawalQueueArray() external view returns (address[] memory, uint256) { - WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - uint256 withdrawalQueueLengthCached = $.withdrawalQueue.length; - - return ($.withdrawalQueue, withdrawalQueueLengthCached); - } - - /// @notice Return the withdrawal queue length. - /// @return uint256 length. - function withdrawalQueueLength() external view returns (uint256) { - WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - - return $.withdrawalQueue.length; - } - - /// @dev Check if the msg.sender is the aggregation vault. - function _isCallerAggregationVault() private view { - WithdrawalQueueStorage storage $ = _getWithdrawalQueueStorage(); - - if (msg.sender != $.eulerAggregationVault) revert NotAuthorized(); - } - - /// @dev Return storage pointer. - /// @return $ WithdrawalQueueStorage storage struct. - function _getWithdrawalQueueStorage() private pure returns (WithdrawalQueueStorage storage $) { - assembly { - $.slot := WithdrawalQueueStorageLocation - } - } -} diff --git a/test/A16zPropertyTests.t.sol b/test/A16zPropertyTests.t.sol index 91de5c7e..05954319 100644 --- a/test/A16zPropertyTests.t.sol +++ b/test/A16zPropertyTests.t.sol @@ -9,8 +9,8 @@ import {Hooks} from "../src/core/module/Hooks.sol"; import {Rewards} from "../src/core/module/Rewards.sol"; import {Fee} from "../src/core/module/Fee.sol"; import {Rebalance} from "../src/core/module/Rebalance.sol"; +import {WithdrawalQueue} from "../src/core/module/WithdrawalQueue.sol"; import {EulerAggregationVaultFactory} from "../src/core/EulerAggregationVaultFactory.sol"; -import {WithdrawalQueue} from "../src/plugin/WithdrawalQueue.sol"; import {Strategy} from "../src/core/module/Strategy.sol"; // mocks import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol"; @@ -26,8 +26,7 @@ contract A16zPropertyTests is ERC4626Test { Fee feeModuleImpl; Strategy strategyModuleImpl; Rebalance rebalanceModuleImpl; - // plugins - WithdrawalQueue withdrawalQueuePluginImpl; + WithdrawalQueue withdrawalQueueModuleImpl; EulerAggregationVaultFactory eulerAggregationVaultFactory; EulerAggregationVault eulerAggregationVault; @@ -40,8 +39,7 @@ contract A16zPropertyTests is ERC4626Test { feeModuleImpl = new Fee(); strategyModuleImpl = new Strategy(); rebalanceModuleImpl = new Rebalance(); - - withdrawalQueuePluginImpl = new WithdrawalQueue(); + withdrawalQueueModuleImpl = new WithdrawalQueue(); EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ owner: factoryOwner, @@ -50,15 +48,15 @@ contract A16zPropertyTests is ERC4626Test { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), strategyModuleImpl: address(strategyModuleImpl), - rebalanceModuleImpl: address(rebalanceModuleImpl) + rebalanceModuleImpl: address(rebalanceModuleImpl), + withdrawalQueueModuleImpl: address(withdrawalQueueModuleImpl) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); vm.prank(factoryOwner); - // eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueuePluginImpl)); _underlying_ = address(new ERC20Mock()); _vault_ = eulerAggregationVaultFactory.deployEulerAggregationVault( - address(withdrawalQueuePluginImpl), _underlying_, "E20M_Agg", "E20M_Agg", CASH_RESERVE_ALLOCATION_POINTS + _underlying_, "E20M_Agg", "E20M_Agg", CASH_RESERVE_ALLOCATION_POINTS ); _delta_ = 0; _vaultMayBeEmpty = false; diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index 055dd956..bbbcda0c 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; // interfaces import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; -import {IWithdrawalQueue} from "../../src/core/interface/IWithdrawalQueue.sol"; // contracts import "evk/test/unit/evault/EVaultTestBase.t.sol"; import {EulerAggregationVault, IEulerAggregationVault} from "../../src/core/EulerAggregationVault.sol"; @@ -11,8 +10,8 @@ import {Hooks, HooksModule} from "../../src/core/module/Hooks.sol"; import {Rewards} from "../../src/core/module/Rewards.sol"; import {Fee} from "../../src/core/module/Fee.sol"; import {Rebalance} from "../../src/core/module/Rebalance.sol"; +import {WithdrawalQueue} from "../../src/core/module/WithdrawalQueue.sol"; import {EulerAggregationVaultFactory} from "../../src/core/EulerAggregationVaultFactory.sol"; -import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; import {Strategy} from "../../src/core/module/Strategy.sol"; // libs import {ErrorsLib} from "../../src/core/lib/ErrorsLib.sol"; @@ -35,12 +34,10 @@ contract EulerAggregationVaultBase is EVaultTestBase { Fee feeModuleImpl; Strategy strategyModuleImpl; Rebalance rebalanceModuleImpl; - // plugins - WithdrawalQueue withdrawalQueueImpl; + WithdrawalQueue withdrawalQueueModuleImpl; EulerAggregationVaultFactory eulerAggregationVaultFactory; EulerAggregationVault eulerAggregationVault; - WithdrawalQueue withdrawalQueue; function setUp() public virtual override { super.setUp(); @@ -56,8 +53,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { feeModuleImpl = new Fee(); strategyModuleImpl = new Strategy(); rebalanceModuleImpl = new Rebalance(); - - withdrawalQueueImpl = new WithdrawalQueue(); + withdrawalQueueModuleImpl = new WithdrawalQueue(); EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ owner: deployer, @@ -66,33 +62,27 @@ contract EulerAggregationVaultBase is EVaultTestBase { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), strategyModuleImpl: address(strategyModuleImpl), - rebalanceModuleImpl: address(rebalanceModuleImpl) + rebalanceModuleImpl: address(rebalanceModuleImpl), + withdrawalQueueModuleImpl: address(withdrawalQueueModuleImpl) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); - // eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueueImpl)); - eulerAggregationVault = EulerAggregationVault( eulerAggregationVaultFactory.deployEulerAggregationVault( - address(withdrawalQueueImpl), - address(assetTST), - "assetTST_Agg", - "assetTST_Agg", - CASH_RESERVE_ALLOCATION_POINTS + address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS ) ); - withdrawalQueue = WithdrawalQueue(eulerAggregationVault.withdrawalQueue()); // grant admin roles to deployer eulerAggregationVault.grantRole(eulerAggregationVault.GUARDIAN_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); - withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); + // withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager eulerAggregationVault.grantRole(eulerAggregationVault.GUARDIAN(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager); - withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); + // withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); vm.stopPrank(); @@ -120,18 +110,18 @@ contract EulerAggregationVaultBase is EVaultTestBase { eulerAggregationVault.getRoleAdmin(eulerAggregationVault.AGGREGATION_VAULT_MANAGER()), eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN() ); - assertEq( - withdrawalQueue.getRoleAdmin(withdrawalQueue.WITHDRAW_QUEUE_MANAGER()), - withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN() - ); + // assertEq( + // withdrawalQueue.getRoleAdmin(withdrawalQueue.WITHDRAW_QUEUE_MANAGER()), + // withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN() + // ); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_OPERATOR_ADMIN(), deployer)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer)); - assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); + // assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_OPERATOR(), manager)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager)); - assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); + // assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); // assertEq(eulerAggregationVaultFactory.getWithdrawalQueueImplsListLength(), 1); // address[] memory withdrawalQueueList = eulerAggregationVaultFactory.getWithdrawalQueueImplsList(); @@ -179,9 +169,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { function testDeployEulerAggregationVaultWithInvalidInitialCashAllocationPoints() public { vm.expectRevert(ErrorsLib.InitialAllocationPointsZero.selector); - eulerAggregationVaultFactory.deployEulerAggregationVault( - address(withdrawalQueueImpl), address(assetTST), "assetTST_Agg", "assetTST_Agg", 0 - ); + eulerAggregationVaultFactory.deployEulerAggregationVault(address(assetTST), "assetTST_Agg", "assetTST_Agg", 0); } function _addStrategy(address from, address strategy, uint256 allocationPoints) internal { @@ -189,19 +177,12 @@ contract EulerAggregationVaultBase is EVaultTestBase { eulerAggregationVault.addStrategy(strategy, allocationPoints); } - function _getWithdrawalQueueLength() internal view returns (uint256) { - uint256 length = withdrawalQueue.withdrawalQueueLength(); - - return length; - } - function _getWithdrawalQueue() internal view returns (address[] memory) { - uint256 length = withdrawalQueue.withdrawalQueueLength(); + return eulerAggregationVault.withdrawalQueue(); + } - address[] memory queue = new address[](length); - for (uint256 i = 0; i < length; ++i) { - queue[i] = withdrawalQueue.getWithdrawalQueueAtIndex(i); - } - return queue; + function _getWithdrawalQueueLength() internal view returns (uint256) { + address[] memory withdrawalQueueArray = eulerAggregationVault.withdrawalQueue(); + return withdrawalQueueArray.length; } } diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index aabd1fda..eb21ab0b 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -32,18 +32,14 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), strategyModuleImpl: address(strategyModuleImpl), - rebalanceModuleImpl: address(rebalanceModuleImpl) + rebalanceModuleImpl: address(rebalanceModuleImpl), + withdrawalQueueModuleImpl: address(withdrawalQueueModuleImpl) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); - // eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueueImpl)); eulerAggregationVault = EulerAggregationVault( eulerAggregationVaultFactory.deployEulerAggregationVault( - address(withdrawalQueueImpl), - address(assetTST), - "assetTST_Agg", - "assetTST_Agg", - CASH_RESERVE_ALLOCATION_POINTS + address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS ) ); @@ -51,13 +47,13 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { eulerAggregationVault.grantRole(eulerAggregationVault.GUARDIAN_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); - withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); + // withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager eulerAggregationVault.grantRole(eulerAggregationVault.GUARDIAN(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager); - withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); + // withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; diff --git a/test/echidna/CryticERC4626TestsHarness.t.sol b/test/echidna/CryticERC4626TestsHarness.t.sol index 92b72bc9..4bb9c69b 100644 --- a/test/echidna/CryticERC4626TestsHarness.t.sol +++ b/test/echidna/CryticERC4626TestsHarness.t.sol @@ -9,8 +9,8 @@ import {Hooks} from "../../src/core/module/Hooks.sol"; import {Rewards} from "../../src/core/module/Rewards.sol"; import {Fee} from "../../src/core/module/Fee.sol"; import {Rebalance} from "../../src/core/module/Rebalance.sol"; +import {WithdrawalQueue} from "../../src/core/module/WithdrawalQueue.sol"; import {EulerAggregationVaultFactory} from "../../src/core/EulerAggregationVaultFactory.sol"; -import {WithdrawalQueue} from "../../src/plugin/WithdrawalQueue.sol"; import {Strategy} from "../../src/core/module/Strategy.sol"; import {TestERC20Token} from "crytic-properties/ERC4626/util/TestERC20Token.sol"; @@ -25,8 +25,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { Fee feeModuleImpl; Strategy strategyModuleImpl; Rebalance rebalanceModuleImpl; - // plugins - WithdrawalQueue withdrawalQueuePluginImpl; + WithdrawalQueue withdrawalQueueModuleImpl; EulerAggregationVaultFactory eulerAggregationVaultFactory; EulerAggregationVault eulerAggregationVault; @@ -37,8 +36,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { feeModuleImpl = new Fee(); strategyModuleImpl = new Strategy(); rebalanceModuleImpl = new Rebalance(); - - withdrawalQueuePluginImpl = new WithdrawalQueue(); + withdrawalQueueModuleImpl = new WithdrawalQueue(); EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ owner: address(this), @@ -47,14 +45,14 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { hooksModuleImpl: address(hooksImpl), feeModuleImpl: address(feeModuleImpl), strategyModuleImpl: address(strategyModuleImpl), - rebalanceModuleImpl: address(rebalanceModuleImpl) + rebalanceModuleImpl: address(rebalanceModuleImpl), + withdrawalQueueModuleImpl: address(withdrawalQueueModuleImpl) }); eulerAggregationVaultFactory = new EulerAggregationVaultFactory(factoryParams); - // eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(address(withdrawalQueuePluginImpl)); TestERC20Token _asset = new TestERC20Token("Test Token", "TT", 18); address _vault = eulerAggregationVaultFactory.deployEulerAggregationVault( - address(withdrawalQueuePluginImpl), address(_asset), "TT_Agg", "TT_Agg", CASH_RESERVE_ALLOCATION_POINTS + address(_asset), "TT_Agg", "TT_Agg", CASH_RESERVE_ALLOCATION_POINTS ); initialize(address(_vault), address(_asset), false); diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 0fab410d..08c5da8a 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.0; import { EulerAggregationVaultBase, EulerAggregationVault, - IWithdrawalQueue, IEVault, TestERC20, IEulerAggregationVault, @@ -13,14 +12,12 @@ import { import {Actor} from "./util/Actor.sol"; import {Strategy} from "./util/Strategy.sol"; import {EulerAggregationVaultHandler} from "./handler/EulerAggregationVaultHandler.sol"; -import {WithdrawalQueueHandler} from "./handler/WithdrawalQueueHandler.sol"; contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { Actor internal actorUtil; Strategy internal strategyUtil; EulerAggregationVaultHandler internal eulerAggregationVaultHandler; - WithdrawalQueueHandler internal withdrawalQueueHandler; // other strategies IEVault eTSTsecond; @@ -49,13 +46,9 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { // cash reserve strategy strategyUtil.includeStrategy(address(0)); - eulerAggregationVaultHandler = - new EulerAggregationVaultHandler(eulerAggregationVault, actorUtil, strategyUtil, withdrawalQueue); - withdrawalQueueHandler = - new WithdrawalQueueHandler(eulerAggregationVault, actorUtil, strategyUtil, withdrawalQueue); + eulerAggregationVaultHandler = new EulerAggregationVaultHandler(eulerAggregationVault, actorUtil, strategyUtil); targetContract(address(eulerAggregationVaultHandler)); - targetContract(address(withdrawalQueueHandler)); } // Right after gulp, total assets allocatable should be always equal to total assets deposited + interest left. @@ -84,14 +77,11 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { // total allocation points should be equal to the sum of the allocation points of all strategies. function invariant_totalAllocationPoints() public view { - address withdrawalQueueAddr = eulerAggregationVault.withdrawalQueue(); - - (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = - IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); + address[] memory withdrawalQueueArray = eulerAggregationVault.getWithdrawalQueueArray(); uint256 expectedTotalAllocationpoints; expectedTotalAllocationpoints += (eulerAggregationVault.getStrategy(address(0))).allocationPoints; - for (uint256 i; i < withdrawalQueueLength; i++) { + for (uint256 i; i < withdrawalQueueArray.length; i++) { IEulerAggregationVault.Strategy memory strategy = eulerAggregationVault.getStrategy(withdrawalQueueArray[i]); if (strategy.status == IEulerAggregationVault.StrategyStatus.Active) { @@ -106,19 +96,19 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { // (2) If length > 0 and the total allocation points == cash reserve allocation points, then every strategy should have a 0 allocation points or should be a strategy in EMERGENCY mode. // (3) withdrawal queue length should always be equal the ghost length variable. function invariant_withdrawalQueue() public view { - address withdrawalQueueAddr = eulerAggregationVault.withdrawalQueue(); - - (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = - IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); + (address[] memory withdrawalQueueArray) = eulerAggregationVault.getWithdrawalQueueArray(); uint256 cashReserveAllocationPoints = (eulerAggregationVault.getStrategy(address(0))).allocationPoints; - if (withdrawalQueueLength == 0) { + if (withdrawalQueueArray.length == 0) { assertEq(eulerAggregationVault.totalAllocationPoints(), cashReserveAllocationPoints); } - if (withdrawalQueueLength > 0 && eulerAggregationVault.totalAllocationPoints() == cashReserveAllocationPoints) { - for (uint256 i; i < withdrawalQueueLength; i++) { + if ( + withdrawalQueueArray.length > 0 + && eulerAggregationVault.totalAllocationPoints() == cashReserveAllocationPoints + ) { + for (uint256 i; i < withdrawalQueueArray.length; i++) { IEulerAggregationVault.Strategy memory strategy = eulerAggregationVault.getStrategy(withdrawalQueueArray[i]); assertEq( @@ -128,18 +118,15 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { } } - assertEq(withdrawalQueueLength, eulerAggregationVaultHandler.ghost_withdrawalQueueLength()); + assertEq(withdrawalQueueArray.length, eulerAggregationVaultHandler.ghost_withdrawalQueueLength()); } // total allocated amount should always be equal the sum of allocated amount in all the strategies. function invariant_totalAllocated() public view { - address withdrawalQueueAddr = eulerAggregationVault.withdrawalQueue(); - - (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = - IWithdrawalQueue(withdrawalQueueAddr).getWithdrawalQueueArray(); + (address[] memory withdrawalQueueArray) = eulerAggregationVault.getWithdrawalQueueArray(); uint256 aggregatedAllocatedAmount; - for (uint256 i; i < withdrawalQueueLength; i++) { + for (uint256 i; i < withdrawalQueueArray.length; i++) { IEulerAggregationVault.Strategy memory strategy = eulerAggregationVault.getStrategy(withdrawalQueueArray[i]); if (strategy.status == IEulerAggregationVault.StrategyStatus.Active) { diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index 5774abe4..c9a6b8ae 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -13,7 +13,6 @@ import { IEulerAggregationVault, ErrorsLib, IERC4626, - WithdrawalQueue, AggAmountCapLib, AggAmountCap } from "../../common/EulerAggregationVaultBase.t.sol"; @@ -27,7 +26,6 @@ contract EulerAggregationVaultHandler is Test { Actor internal actorUtil; Strategy internal strategyUtil; EulerAggregationVault internal eulerAggVault; - WithdrawalQueue internal withdrawalQueue; // ghost vars uint256 public ghost_totalAllocationPoints; @@ -43,16 +41,10 @@ contract EulerAggregationVaultHandler is Test { bool success; bytes returnData; - constructor( - EulerAggregationVault _eulerAggVault, - Actor _actor, - Strategy _strategy, - WithdrawalQueue _withdrawalQueue - ) { + constructor(EulerAggregationVault _eulerAggVault, Actor _actor, Strategy _strategy) { eulerAggVault = _eulerAggVault; actorUtil = _actor; strategyUtil = _strategy; - withdrawalQueue = _withdrawalQueue; // initiating ghost total allocation points to match count cash reserve. ghost_totalAllocationPoints += eulerAggVault.totalAllocationPoints(); @@ -193,17 +185,25 @@ contract EulerAggregationVaultHandler is Test { assertEq(eulerAggVault.totalAllocationPoints(), ghost_totalAllocationPoints); } + function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external { + (currentActor, success, returnData) = actorUtil.initiateExactActorCall( + 0, + address(eulerAggVault), + abi.encodeWithSelector(EulerAggregationVault.reorderWithdrawalQueue.selector, _index1, _index2) + ); + } + function rebalance(uint256 _actorIndexSeed) external { (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); - (address[] memory strategiesToRebalance, uint256 strategiesCounter) = withdrawalQueue.getWithdrawalQueueArray(); + (address[] memory strategiesToRebalance) = eulerAggVault.getWithdrawalQueueArray(); (currentActor, success, returnData) = actorUtil.initiateActorCall( _actorIndexSeed, address(eulerAggVault), abi.encodeWithSelector(EulerAggregationVault.rebalance.selector, strategiesToRebalance) ); - for (uint256 i; i < strategiesCounter; i++) { + for (uint256 i; i < strategiesToRebalance.length; i++) { assertEq( IERC4626(strategiesToRebalance[i]).maxWithdraw(address(eulerAggVault)), (eulerAggVault.getStrategy(strategiesToRebalance[i])).allocated @@ -220,10 +220,9 @@ contract EulerAggregationVaultHandler is Test { uint256 accumulatedPerformanceFee; if (feeRecipient != address(0) && performanceFee > 0) { accumulatedPerformanceFee = ghost_accumulatedPerformanceFeePerRecipient[feeRecipient]; - (address[] memory withdrawalQueueArray, uint256 withdrawalQueueLength) = - withdrawalQueue.getWithdrawalQueueArray(); + address[] memory withdrawalQueueArray = eulerAggVault.getWithdrawalQueueArray(); - for (uint256 i; i < withdrawalQueueLength; i++) { + for (uint256 i; i < withdrawalQueueArray.length; i++) { uint256 allocated = (eulerAggVault.getStrategy(withdrawalQueueArray[i])).allocated; uint256 underlying = IERC4626(withdrawalQueueArray[i]).maxWithdraw(address(eulerAggVault)); if (underlying >= allocated) { diff --git a/test/invariant/handler/WithdrawalQueueHandler.sol b/test/invariant/handler/WithdrawalQueueHandler.sol deleted file mode 100644 index 0598d090..00000000 --- a/test/invariant/handler/WithdrawalQueueHandler.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import { - Test, - EulerAggregationVaultBase, - EulerAggregationVault, - console2, - EVault, - IEVault, - IRMTestDefault, - TestERC20, - IEulerAggregationVault, - ErrorsLib, - IERC4626, - WithdrawalQueue -} from "../../common/EulerAggregationVaultBase.t.sol"; -import {Actor} from "../util/Actor.sol"; -import {Strategy} from "../util/Strategy.sol"; - -contract WithdrawalQueueHandler is Test { - Actor internal actorUtil; - Strategy internal strategyUtil; - EulerAggregationVault internal eulerAggVault; - WithdrawalQueue internal withdrawalQueue; - - // last function call state - address currentActor; - uint256 currentActorIndex; - bool success; - bytes returnData; - - constructor( - EulerAggregationVault _eulerAggVault, - Actor _actor, - Strategy _strategy, - WithdrawalQueue _withdrawalQueue - ) { - eulerAggVault = _eulerAggVault; - actorUtil = _actor; - strategyUtil = _strategy; - withdrawalQueue = _withdrawalQueue; - } - - function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external { - (currentActor, success, returnData) = actorUtil.initiateExactActorCall( - 0, - address(withdrawalQueue), - abi.encodeWithSelector(WithdrawalQueue.reorderWithdrawalQueue.selector, _index1, _index2) - ); - } -} diff --git a/test/unit/HarvestTest.t.sol b/test/unit/HarvestTest.t.sol index 4730d534..18b796d0 100644 --- a/test/unit/HarvestTest.t.sol +++ b/test/unit/HarvestTest.t.sol @@ -258,11 +258,4 @@ contract HarvestTest is EulerAggregationVaultBase { assertApproxEqAbs(user1AssetsAfter, expectedUser1Assets + interestToBeAccrued, 1); assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore + interestToBeAccrued); } - - function testCallingHarvestMethodsFromRandomSender() public { - vm.startPrank(user1); - vm.expectRevert(ErrorsLib.NotWithdrawaQueue.selector); - eulerAggregationVault.executeStrategyWithdraw(address(eTST), 1); - vm.stopPrank(); - } } diff --git a/test/unit/ReorderWithdrawalQueueTest.t.sol b/test/unit/ReorderWithdrawalQueueTest.t.sol index 722dc930..cfed5471 100644 --- a/test/unit/ReorderWithdrawalQueueTest.t.sol +++ b/test/unit/ReorderWithdrawalQueueTest.t.sol @@ -5,7 +5,8 @@ import { EulerAggregationVaultBase, EulerAggregationVault, IEVault, - WithdrawalQueue + WithdrawalQueue, + ErrorsLib } from "../common/EulerAggregationVaultBase.t.sol"; contract ReorderWithdrawalQueueTest is EulerAggregationVaultBase { @@ -36,7 +37,7 @@ contract ReorderWithdrawalQueueTest is EulerAggregationVaultBase { ); vm.prank(manager); - withdrawalQueue.reorderWithdrawalQueue(0, 1); + eulerAggregationVault.reorderWithdrawalQueue(0, 1); assertEq( eulerAggregationVault.getStrategy(_getWithdrawalQueue()[0]).allocationPoints, eTSTsecondaryAllocationPoints @@ -46,15 +47,15 @@ contract ReorderWithdrawalQueueTest is EulerAggregationVaultBase { function testReorderWithdrawalQueueWhenOutOfBounds() public { vm.startPrank(manager); - vm.expectRevert(WithdrawalQueue.OutOfBounds.selector); - withdrawalQueue.reorderWithdrawalQueue(0, 3); + vm.expectRevert(ErrorsLib.OutOfBounds.selector); + eulerAggregationVault.reorderWithdrawalQueue(0, 3); vm.stopPrank(); } function testReorderWithdrawalQueueWhenSameIndex() public { vm.startPrank(manager); - vm.expectRevert(WithdrawalQueue.SameIndexes.selector); - withdrawalQueue.reorderWithdrawalQueue(0, 0); + vm.expectRevert(ErrorsLib.SameIndexes.selector); + eulerAggregationVault.reorderWithdrawalQueue(0, 0); vm.stopPrank(); } } From 8a5283ad94449f3f5530f2da371e60074eab4e50 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:23:01 +0300 Subject: [PATCH 289/316] import WithdrawalQueueModule --- src/core/Dispatch.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/Dispatch.sol b/src/core/Dispatch.sol index 96ea109f..8916d5ea 100644 --- a/src/core/Dispatch.sol +++ b/src/core/Dispatch.sol @@ -7,7 +7,7 @@ import {RewardsModule} from "./module/Rewards.sol"; import {StrategyModule} from "./module/Strategy.sol"; import {FeeModule} from "./module/Fee.sol"; import {RebalanceModule} from "./module/Rebalance.sol"; -import {WithdrawalQueue} from "./module/WithdrawalQueue.sol"; +import {WithdrawalQueueModule} from "./module/WithdrawalQueue.sol"; /// @title Dispatch contract /// @custom:security-contact security@euler.xyz @@ -20,7 +20,7 @@ abstract contract Dispatch is FeeModule, StrategyModule, RebalanceModule, - WithdrawalQueue + WithdrawalQueueModule { address public immutable rewardsModule; address public immutable hooksModule; From bde11836bd00b413276ae4d242f695cb71e9ab5e Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:27:32 +0300 Subject: [PATCH 290/316] clean --- src/core/EulerAggregationVault.sol | 39 ++++++-------- src/core/lib/StorageLib.sol | 2 - src/core/module/WithdrawalQueue.sol | 15 +----- test/common/EulerAggregationVaultBase.t.sol | 54 +++---------------- test/e2e/BalanceForwarderE2ETest.t.sol | 4 +- .../EulerAggregationLayerInvariants.t.sol | 6 +-- .../handler/EulerAggregationVaultHandler.sol | 4 +- 7 files changed, 33 insertions(+), 91 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 13bafe56..3bd8ac0b 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -48,6 +48,8 @@ contract EulerAggregationVault is bytes32 public constant STRATEGY_OPERATOR_ADMIN = keccak256("STRATEGY_OPERATOR_ADMIN"); bytes32 public constant AGGREGATION_VAULT_MANAGER = keccak256("AGGREGATION_VAULT_MANAGER"); bytes32 public constant AGGREGATION_VAULT_MANAGER_ADMIN = keccak256("AGGREGATION_VAULT_MANAGER_ADMIN"); + bytes32 public constant WITHDRAWAL_QUEUE_MANAGER = keccak256("WITHDRAWAL_QUEUE_MANAGER"); + bytes32 public constant WITHDRAWAL_QUEUE_MANAGER_ADMIN = keccak256("WITHDRAWAL_QUEUE_MANAGER_ADMIN"); /// @dev Constructor. constructor(ConstructorParams memory _constructorParams) @@ -88,6 +90,7 @@ contract EulerAggregationVault is _setRoleAdmin(GUARDIAN, GUARDIAN_ADMIN); _setRoleAdmin(STRATEGY_OPERATOR, STRATEGY_OPERATOR_ADMIN); _setRoleAdmin(AGGREGATION_VAULT_MANAGER, AGGREGATION_VAULT_MANAGER_ADMIN); + _setRoleAdmin(WITHDRAWAL_QUEUE_MANAGER, WITHDRAWAL_QUEUE_MANAGER_ADMIN); } /// @dev See {FeeModule-setFeeRecipient}. @@ -187,12 +190,19 @@ contract EulerAggregationVault is function rebalance(address[] calldata _strategies) external override use(rebalanceModule) {} - function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) public override { - super.reorderWithdrawalQueue(_index1, _index2); - } + function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) + external + override + onlyRole(WITHDRAWAL_QUEUE_MANAGER) + use(withdrawalQueueModule) + {} - function withdrawalQueue() public view override returns (address[] memory) { - return super.withdrawalQueue(); + /// @notice Return the withdrawal queue length. + /// @return uint256 length. + function withdrawalQueue() public view virtual returns (address[] memory) { + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + + return $.withdrawalQueue; } /// @notice Harvest all the strategies. Any positive yiled should be gupled by calling gulp() after harvesting. @@ -269,29 +279,12 @@ contract EulerAggregationVault is /// @notice Get the performance fee config. /// @return adddress Fee recipient. /// @return uint256 Fee percentage. - function performanceFeeConfig() external view returns (address, uint256) { + function performanceFeeConfig() external view virtual returns (address, uint256) { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); return ($.feeRecipient, $.performanceFee); } - /// @notice Get strategy address from withdrawal queue by index. - /// @param _index Index to fetch. - /// @return address Strategy address. - function getWithdrawalQueueAtIndex(uint256 _index) external view returns (address) { - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - - return $.withdrawalQueue[_index]; - } - - /// @notice Get the withdrawal queue array and it's length. - /// @return withdrawalQueueMem The withdrawal queue array in memory. - function getWithdrawalQueueArray() external view returns (address[] memory) { - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - - return $.withdrawalQueue; - } - /// @dev See {IERC4626-deposit}. /// @dev Call DEPOSIT hook if enabled. function deposit(uint256 _assets, address _receiver) public override nonReentrant returns (uint256) { diff --git a/src/core/lib/StorageLib.sol b/src/core/lib/StorageLib.sol index d5fd433c..29715e7e 100644 --- a/src/core/lib/StorageLib.sol +++ b/src/core/lib/StorageLib.sol @@ -16,8 +16,6 @@ struct AggregationVaultStorage { uint256 performanceFee; /// fee recipient address address feeRecipient; - // /// WithdrawalQueue plugin address - // address withdrawalQueue; /// Mapping between a strategy address and it's allocation config mapping(address => IEulerAggregationVault.Strategy) strategies; /// @dev An array of strategy addresses to withdraw from diff --git a/src/core/module/WithdrawalQueue.sol b/src/core/module/WithdrawalQueue.sol index 1ba994e7..491503a0 100644 --- a/src/core/module/WithdrawalQueue.sol +++ b/src/core/module/WithdrawalQueue.sol @@ -13,16 +13,13 @@ import {StorageLib as Storage, AggregationVaultStorage} from "../lib/StorageLib. import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; abstract contract WithdrawalQueueModule is Shared { - // bytes32 public constant WITHDRAW_QUEUE_MANAGER = keccak256("WITHDRAW_QUEUE_MANAGER"); - // bytes32 public constant WITHDRAW_QUEUE_MANAGER_ADMIN = keccak256("WITHDRAW_QUEUE_MANAGER_ADMIN"); - event ReorderWithdrawalQueue(uint8 index1, uint8 index2); /// @notice Swap two strategies indexes in the withdrawal queue. - /// @dev Can only be called by an address that have the WITHDRAW_QUEUE_MANAGER role. + /// @dev Can only be called by an address that have the WITHDRAWAL_QUEUE_MANAGER role. /// @param _index1 index of first strategy. /// @param _index2 index of second strategy. - function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) public virtual nonReentrant { + function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external virtual nonReentrant { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); uint256 length = $.withdrawalQueue.length; @@ -39,14 +36,6 @@ abstract contract WithdrawalQueueModule is Shared { emit ReorderWithdrawalQueue(_index1, _index2); } - - /// @notice Return the withdrawal queue length. - /// @return uint256 length. - function withdrawalQueue() public view virtual returns (address[] memory) { - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - - return $.withdrawalQueue; - } } contract WithdrawalQueue is WithdrawalQueueModule {} diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index bbbcda0c..1aa0376d 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -76,13 +76,13 @@ contract EulerAggregationVaultBase is EVaultTestBase { eulerAggregationVault.grantRole(eulerAggregationVault.GUARDIAN_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); - // withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.WITHDRAWAL_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager eulerAggregationVault.grantRole(eulerAggregationVault.GUARDIAN(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager); - // withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.WITHDRAWAL_QUEUE_MANAGER(), manager); vm.stopPrank(); @@ -110,23 +110,18 @@ contract EulerAggregationVaultBase is EVaultTestBase { eulerAggregationVault.getRoleAdmin(eulerAggregationVault.AGGREGATION_VAULT_MANAGER()), eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN() ); - // assertEq( - // withdrawalQueue.getRoleAdmin(withdrawalQueue.WITHDRAW_QUEUE_MANAGER()), - // withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN() - // ); + assertEq( + eulerAggregationVault.getRoleAdmin(eulerAggregationVault.WITHDRAWAL_QUEUE_MANAGER()), + eulerAggregationVault.WITHDRAWAL_QUEUE_MANAGER_ADMIN() + ); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_OPERATOR_ADMIN(), deployer)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer)); - // assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.WITHDRAWAL_QUEUE_MANAGER_ADMIN(), deployer)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.STRATEGY_OPERATOR(), manager)); assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager)); - // assertTrue(withdrawalQueue.hasRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager)); - - // assertEq(eulerAggregationVaultFactory.getWithdrawalQueueImplsListLength(), 1); - // address[] memory withdrawalQueueList = eulerAggregationVaultFactory.getWithdrawalQueueImplsList(); - // assertEq(withdrawalQueueList.length, 1); - // assertEq(address(withdrawalQueueList[0]), address(withdrawalQueueImpl)); + assertTrue(eulerAggregationVault.hasRole(eulerAggregationVault.WITHDRAWAL_QUEUE_MANAGER(), manager)); assertEq(eulerAggregationVaultFactory.getAggregationVaultsListLength(), 1); address[] memory aggregationVaultsList = eulerAggregationVaultFactory.getAggregationVaultsListSlice(0, 1); @@ -134,39 +129,6 @@ contract EulerAggregationVaultBase is EVaultTestBase { assertEq(address(aggregationVaultsList[0]), address(eulerAggregationVault)); } - // function testWhitelistWithdrawalQueueImpl() public { - // address newWithdrawalQueueImpl = makeAddr("NEW_WITHDRAWAL_QUEUE_IMPL"); - - // vm.prank(deployer); - // eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(newWithdrawalQueueImpl); - - // assertEq(eulerAggregationVaultFactory.isWhitelistedWithdrawalQueueImpl(newWithdrawalQueueImpl), true); - // address[] memory withdrawalQueueList = eulerAggregationVaultFactory.getWithdrawalQueueImplsList(); - // assertEq(withdrawalQueueList.length, 2); - // assertEq(address(withdrawalQueueList[1]), address(newWithdrawalQueueImpl)); - - // vm.prank(deployer); - // vm.expectRevert(EulerAggregationVaultFactory.WithdrawalQueueAlreadyWhitelisted.selector); - // eulerAggregationVaultFactory.whitelistWithdrawalQueueImpl(newWithdrawalQueueImpl); - // } - - // function testDeployEulerAggregationVaultWithRandomWithdrawalQueue() public { - // address newWithdrawalQueueImpl = makeAddr("NEW_WITHDRAWAL_QUEUE_IMPL"); - - // vm.expectRevert(EulerAggregationVaultFactory.NotWhitelistedWithdrawalQueueImpl.selector); - // eulerAggregationVaultFactory.deployEulerAggregationVault( - // newWithdrawalQueueImpl, address(assetTST), "assetTST_Agg", "assetTST_Agg", CASH_RESERVE_ALLOCATION_POINTS - // ); - - // address[] memory aggregationVaultsList = - // eulerAggregationVaultFactory.getAggregationVaultsListSlice(0, type(uint256).max); - // assertEq(aggregationVaultsList.length, 1); - // assertEq(address(aggregationVaultsList[0]), address(eulerAggregationVault)); - - // vm.expectRevert(EulerAggregationVaultFactory.InvalidQuery.selector); - // eulerAggregationVaultFactory.getAggregationVaultsListSlice(1, 0); - // } - function testDeployEulerAggregationVaultWithInvalidInitialCashAllocationPoints() public { vm.expectRevert(ErrorsLib.InitialAllocationPointsZero.selector); eulerAggregationVaultFactory.deployEulerAggregationVault(address(assetTST), "assetTST_Agg", "assetTST_Agg", 0); diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index eb21ab0b..f1ad8b56 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -47,13 +47,13 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { eulerAggregationVault.grantRole(eulerAggregationVault.GUARDIAN_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR_ADMIN(), deployer); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER_ADMIN(), deployer); - // withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER_ADMIN(), deployer); + eulerAggregationVault.grantRole(eulerAggregationVault.WITHDRAWAL_QUEUE_MANAGER_ADMIN(), deployer); // grant roles to manager eulerAggregationVault.grantRole(eulerAggregationVault.GUARDIAN(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.STRATEGY_OPERATOR(), manager); eulerAggregationVault.grantRole(eulerAggregationVault.AGGREGATION_VAULT_MANAGER(), manager); - // withdrawalQueue.grantRole(withdrawalQueue.WITHDRAW_QUEUE_MANAGER(), manager); + eulerAggregationVault.grantRole(eulerAggregationVault.WITHDRAWAL_QUEUE_MANAGER(), manager); vm.stopPrank(); uint256 initialStrategyAllocationPoints = 500e18; diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 08c5da8a..00ffb51f 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -77,7 +77,7 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { // total allocation points should be equal to the sum of the allocation points of all strategies. function invariant_totalAllocationPoints() public view { - address[] memory withdrawalQueueArray = eulerAggregationVault.getWithdrawalQueueArray(); + address[] memory withdrawalQueueArray = eulerAggregationVault.withdrawalQueue(); uint256 expectedTotalAllocationpoints; expectedTotalAllocationpoints += (eulerAggregationVault.getStrategy(address(0))).allocationPoints; @@ -96,7 +96,7 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { // (2) If length > 0 and the total allocation points == cash reserve allocation points, then every strategy should have a 0 allocation points or should be a strategy in EMERGENCY mode. // (3) withdrawal queue length should always be equal the ghost length variable. function invariant_withdrawalQueue() public view { - (address[] memory withdrawalQueueArray) = eulerAggregationVault.getWithdrawalQueueArray(); + (address[] memory withdrawalQueueArray) = eulerAggregationVault.withdrawalQueue(); uint256 cashReserveAllocationPoints = (eulerAggregationVault.getStrategy(address(0))).allocationPoints; @@ -123,7 +123,7 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { // total allocated amount should always be equal the sum of allocated amount in all the strategies. function invariant_totalAllocated() public view { - (address[] memory withdrawalQueueArray) = eulerAggregationVault.getWithdrawalQueueArray(); + (address[] memory withdrawalQueueArray) = eulerAggregationVault.withdrawalQueue(); uint256 aggregatedAllocatedAmount; for (uint256 i; i < withdrawalQueueArray.length; i++) { diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index c9a6b8ae..4f4e0941 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -196,7 +196,7 @@ contract EulerAggregationVaultHandler is Test { function rebalance(uint256 _actorIndexSeed) external { (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); - (address[] memory strategiesToRebalance) = eulerAggVault.getWithdrawalQueueArray(); + (address[] memory strategiesToRebalance) = eulerAggVault.withdrawalQueue(); (currentActor, success, returnData) = actorUtil.initiateActorCall( _actorIndexSeed, address(eulerAggVault), @@ -220,7 +220,7 @@ contract EulerAggregationVaultHandler is Test { uint256 accumulatedPerformanceFee; if (feeRecipient != address(0) && performanceFee > 0) { accumulatedPerformanceFee = ghost_accumulatedPerformanceFeePerRecipient[feeRecipient]; - address[] memory withdrawalQueueArray = eulerAggVault.getWithdrawalQueueArray(); + address[] memory withdrawalQueueArray = eulerAggVault.withdrawalQueue(); for (uint256 i; i < withdrawalQueueArray.length; i++) { uint256 allocated = (eulerAggVault.getStrategy(withdrawalQueueArray[i])).allocated; From 7392b00cd0152f612f3625ab53e88311d474406f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 25 Jul 2024 19:03:34 +0300 Subject: [PATCH 291/316] refactor --- src/core/EulerAggregationVault.sol | 28 ++++++++++++++-------------- src/core/common/Shared.sol | 9 +++++++-- src/core/module/Rebalance.sol | 2 +- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 3bd8ac0b..652386e1 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -188,8 +188,10 @@ contract EulerAggregationVault is /// @dev See {RewardsModule-disableBalanceForwarder}. function disableBalanceForwarder() external override use(rewardsModule) {} + /// @dev See {RebalanceModule-rebalance}. function rebalance(address[] calldata _strategies) external override use(rebalanceModule) {} + /// @dev See {WithdrawalQueue-reorderWithdrawalQueue}. function reorderWithdrawalQueue(uint8 _index1, uint8 _index2) external override @@ -197,14 +199,6 @@ contract EulerAggregationVault is use(withdrawalQueueModule) {} - /// @notice Return the withdrawal queue length. - /// @return uint256 length. - function withdrawalQueue() public view virtual returns (address[] memory) { - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - - return $.withdrawalQueue; - } - /// @notice Harvest all the strategies. Any positive yiled should be gupled by calling gulp() after harvesting. /// @dev This function will loop through the strategies following the withdrawal queue order and harvest all. /// @dev Harvest yield and losses will be aggregated and only net yield/loss will be accounted. @@ -215,7 +209,7 @@ contract EulerAggregationVault is } /// @notice update accrued interest - function updateInterestAccrued() external { + function updateInterestAccrued() external nonReentrant { return _updateInterestAccrued(); } @@ -240,7 +234,7 @@ contract EulerAggregationVault is } /// @notice Get saving rate data. - /// @return avsr AggregationVaultSavingRate struct. + /// @return AggregationVaultSavingRate struct. function getAggregationVaultSavingRate() external view returns (AggregationVaultSavingRate memory) { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); AggregationVaultSavingRate memory avsr = AggregationVaultSavingRate({ @@ -285,6 +279,14 @@ contract EulerAggregationVault is return ($.feeRecipient, $.performanceFee); } + /// @notice Return the withdrawal queue length. + /// @return uint256 length. + function withdrawalQueue() external view virtual returns (address[] memory) { + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + + return $.withdrawalQueue; + } + /// @dev See {IERC4626-deposit}. /// @dev Call DEPOSIT hook if enabled. function deposit(uint256 _assets, address _receiver) public override nonReentrant returns (uint256) { @@ -353,10 +355,8 @@ contract EulerAggregationVault is /// @notice get the total assets allocatable /// @dev the total assets allocatable is the amount of assets deposited into the aggregator + assets already deposited into strategies /// @return uint256 total assets - function totalAssetsAllocatable() public view returns (uint256) { - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - - return IERC20(asset()).balanceOf(address(this)) + $.totalAllocated; + function totalAssetsAllocatable() external view returns (uint256) { + return _totalAssetsAllocatable(); } /// @dev See {IERC4626-_deposit}. diff --git a/src/core/common/Shared.sol b/src/core/common/Shared.sol index 2babfbd6..7d4d9215 100644 --- a/src/core/common/Shared.sol +++ b/src/core/common/Shared.sol @@ -106,8 +106,7 @@ abstract contract Shared { // Do not gulp if total supply is too low if (IERC4626(address(this)).totalSupply() < MIN_SHARES_FOR_GULP) return; - uint256 toGulp = - IEulerAggregationVault(address(this)).totalAssetsAllocatable() - $.totalAssetsDeposited - $.interestLeft; + uint256 toGulp = _totalAssetsAllocatable() - $.totalAssetsDeposited - $.interestLeft; if (toGulp == 0) return; uint256 maxGulp = type(uint168).max - $.interestLeft; @@ -156,6 +155,12 @@ abstract contract Shared { return $.interestLeft * timePassed / totalDuration; } + function _totalAssetsAllocatable() internal view returns (uint256) { + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + + return IERC20(IERC4626(address(this)).asset()).balanceOf(address(this)) + $.totalAllocated; + } + /// @dev Revert with call error or EmptyError /// @param _errorMsg call revert message function _revertBytes(bytes memory _errorMsg) private pure { diff --git a/src/core/module/Rebalance.sol b/src/core/module/Rebalance.sol index 62ea9950..2278e693 100644 --- a/src/core/module/Rebalance.sol +++ b/src/core/module/Rebalance.sol @@ -49,7 +49,7 @@ abstract contract RebalanceModule is ContextUpgradeable, Shared { if (strategyData.status != IEulerAggregationVault.StrategyStatus.Active) return; uint256 totalAllocationPointsCache = $.totalAllocationPoints; - uint256 totalAssetsAllocatableCache = IEulerAggregationVault(address(this)).totalAssetsAllocatable(); + uint256 totalAssetsAllocatableCache = _totalAssetsAllocatable(); uint256 targetAllocation = totalAssetsAllocatableCache * strategyData.allocationPoints / totalAllocationPointsCache; From a6320987420a6f02fc4075f388bdb0d14baabdf9 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 25 Jul 2024 19:10:29 +0300 Subject: [PATCH 292/316] refactor hooks storage --- src/core/common/Shared.sol | 12 +++--------- src/core/lib/StorageLib.sol | 3 ++- src/core/module/Hooks.sol | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/core/common/Shared.sol b/src/core/common/Shared.sol index 7d4d9215..ddd6cff5 100644 --- a/src/core/common/Shared.sol +++ b/src/core/common/Shared.sol @@ -72,7 +72,8 @@ abstract contract Shared { if (_hookedFns >= ACTIONS_COUNTER) revert Errors.InvalidHookedFns(); AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - $.hooksConfig = (uint256(uint160(_hooksTarget)) << 32) | uint256(_hookedFns); + $.hooksTarget = _hooksTarget; + $.hookedFns = _hookedFns; } /// @notice Checks whether a hook has been installed for the function and if so, invokes the hook target. @@ -81,7 +82,7 @@ abstract contract Shared { function _callHooksTarget(uint32 _fn, address _caller) internal { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - (address target, uint32 hookedFns) = _getHooksConfig($.hooksConfig); + (address target, uint32 hookedFns) = ($.hooksTarget, $.hookedFns); if (hookedFns.isNotSet(_fn)) return; @@ -90,13 +91,6 @@ abstract contract Shared { if (!success) _revertBytes(data); } - /// @notice Get the hooks contract and the hooked functions. - /// @return address Hooks contract. - /// @return uint32 Hooked functions. - function _getHooksConfig(uint256 _hooksConfig) internal pure returns (address, uint32) { - return (address(uint160(_hooksConfig >> 32)), uint32(_hooksConfig & HOOKS_MASK)); - } - /// @dev gulp positive yield into interest left amd update accrued interest. function _gulp() internal { _updateInterestAccrued(); diff --git a/src/core/lib/StorageLib.sol b/src/core/lib/StorageLib.sol index 29715e7e..804d952f 100644 --- a/src/core/lib/StorageLib.sol +++ b/src/core/lib/StorageLib.sol @@ -39,7 +39,8 @@ struct AggregationVaultStorage { /// @dev storing the hooks target and kooked functions. - uint256 hooksConfig; + address hooksTarget; + uint32 hookedFns; } library StorageLib { diff --git a/src/core/module/Hooks.sol b/src/core/module/Hooks.sol index 0f20379a..2772ae43 100644 --- a/src/core/module/Hooks.sol +++ b/src/core/module/Hooks.sol @@ -29,7 +29,7 @@ abstract contract HooksModule is Shared { function getHooksConfig() external view returns (address, uint32) { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - return _getHooksConfig($.hooksConfig); + return ($.hooksTarget, $.hookedFns); } } From 3de00311088f273c5f6f1291bce183602b195cf7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 26 Jul 2024 00:08:23 +0300 Subject: [PATCH 293/316] Refactor _deductLoss() --- src/core/EulerAggregationVault.sol | 13 ++++------- src/core/common/Shared.sol | 35 ++++++++++++++++++++++-------- src/core/module/Strategy.sol | 4 +++- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 652386e1..d274e850 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -418,9 +418,6 @@ contract EulerAggregationVault is /// @dev Loop through stratgies, aggregate positive yield and loss and account for net amount. /// @dev Loss socialization will be taken out from interest left first, if not enough, socialize on deposits. function _harvest() internal { - // gulp any extra tokens to cover in case of loss socialization - _gulp(); - AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); uint256 totalYield; @@ -432,16 +429,14 @@ contract EulerAggregationVault is totalLoss += loss; } - $.totalAllocated = $.totalAllocated + totalYield - totalLoss; - if (totalLoss > totalYield) { - // we do not need to call gulp() again here as aggregated yield is negative and there is nothing gulpable. _deductLoss(totalLoss - totalYield); - } else { - // gulp net positive yield - _gulp(); } + $.totalAllocated = $.totalAllocated + totalYield - totalLoss; + + _gulp(); + emit Events.Harvest($.totalAllocated, totalYield, totalLoss); } diff --git a/src/core/common/Shared.sol b/src/core/common/Shared.sol index ddd6cff5..805e791d 100644 --- a/src/core/common/Shared.sol +++ b/src/core/common/Shared.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; +import {Test, console2, stdError} from "forge-std/Test.sol"; + // interfaces import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; @@ -49,16 +51,31 @@ abstract contract Shared { function _deductLoss(uint256 _lostAmount) internal { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - uint168 cachedInterestLeft = $.interestLeft; - if (cachedInterestLeft >= _lostAmount) { - // cut loss from interest left only - cachedInterestLeft -= uint168(_lostAmount); - } else { - // cut the interest left and socialize the diff - $.totalAssetsDeposited -= _lostAmount - cachedInterestLeft; - cachedInterestLeft = 0; + // uint168 cachedInterestLeft = $.interestLeft; + // if (cachedInterestLeft >= _lostAmount) { + // // cut loss from interest left only + // cachedInterestLeft -= uint168(_lostAmount); + // } else { + // // cut the interest left and socialize the diff + // $.totalAssetsDeposited -= _lostAmount - cachedInterestLeft; + // cachedInterestLeft = 0; + // } + // $.interestLeft = cachedInterestLeft; + + uint256 totalAssetsDepositedCache = $.totalAssetsDeposited; + + console2.log("_totalAssetsAllocatable()", _totalAssetsAllocatable()); + console2.log("totalAssetsDepositedCache", totalAssetsDepositedCache); + + uint256 totalNotDistributed = _totalAssetsAllocatable() - totalAssetsDepositedCache; + + $.interestLeft = 0; + if (_lostAmount > totalNotDistributed) { + _lostAmount -= totalNotDistributed; + + // socialize the loss + $.totalAssetsDeposited = totalAssetsDepositedCache - _lostAmount; } - $.interestLeft = cachedInterestLeft; } function _setHooksConfig(address _hooksTarget, uint32 _hookedFns) internal { diff --git a/src/core/module/Strategy.sol b/src/core/module/Strategy.sol index 3a47c455..acd65717 100644 --- a/src/core/module/Strategy.sol +++ b/src/core/module/Strategy.sol @@ -89,10 +89,12 @@ abstract contract StrategyModule is Shared { } else if (strategyCached.status == IEulerAggregationVault.StrategyStatus.Active) { $.strategies[_strategy].status = IEulerAggregationVault.StrategyStatus.Emergency; + _deductLoss(strategyCached.allocated); + $.totalAllocationPoints -= strategyCached.allocationPoints; $.totalAllocated -= strategyCached.allocated; - _deductLoss(strategyCached.allocated); + _gulp(); } else { uint256 vaultStrategyBalance = IERC4626(_strategy).maxWithdraw(address(this)); From a18db08cdcb04d354bcaf27e2d68babc647ca0f8 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 26 Jul 2024 13:02:45 +0300 Subject: [PATCH 294/316] clean --- src/core/EulerAggregationVault.sol | 1 + src/core/common/Shared.sol | 27 ++++++++------------------- src/core/module/Strategy.sol | 1 + 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index d274e850..61da91bc 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -429,6 +429,7 @@ contract EulerAggregationVault is totalLoss += loss; } + // we should deduct loss before decrease totalAllocated to not underflow if (totalLoss > totalYield) { _deductLoss(totalLoss - totalYield); } diff --git a/src/core/common/Shared.sol b/src/core/common/Shared.sol index 805e791d..e92a899c 100644 --- a/src/core/common/Shared.sol +++ b/src/core/common/Shared.sol @@ -48,33 +48,22 @@ abstract contract Shared { $.locked = REENTRANCYLOCK__UNLOCKED; } - function _deductLoss(uint256 _lostAmount) internal { + /// @dev Deduct _lossAmount from not-distributed yet amounts, if not enough, socialize loss. + /// @dev not distributed amount is amount available to gulp + interest left. + /// @param _lossAmount Amount lost. + function _deductLoss(uint256 _lossAmount) internal { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - // uint168 cachedInterestLeft = $.interestLeft; - // if (cachedInterestLeft >= _lostAmount) { - // // cut loss from interest left only - // cachedInterestLeft -= uint168(_lostAmount); - // } else { - // // cut the interest left and socialize the diff - // $.totalAssetsDeposited -= _lostAmount - cachedInterestLeft; - // cachedInterestLeft = 0; - // } - // $.interestLeft = cachedInterestLeft; - uint256 totalAssetsDepositedCache = $.totalAssetsDeposited; - - console2.log("_totalAssetsAllocatable()", _totalAssetsAllocatable()); - console2.log("totalAssetsDepositedCache", totalAssetsDepositedCache); - uint256 totalNotDistributed = _totalAssetsAllocatable() - totalAssetsDepositedCache; + // set interestLeft to zero, will be updated to the right value during _gulp() $.interestLeft = 0; - if (_lostAmount > totalNotDistributed) { - _lostAmount -= totalNotDistributed; + if (_lossAmount > totalNotDistributed) { + _lossAmount -= totalNotDistributed; // socialize the loss - $.totalAssetsDeposited = totalAssetsDepositedCache - _lostAmount; + $.totalAssetsDeposited = totalAssetsDepositedCache - _lossAmount; } } diff --git a/src/core/module/Strategy.sol b/src/core/module/Strategy.sol index acd65717..e2365a6a 100644 --- a/src/core/module/Strategy.sol +++ b/src/core/module/Strategy.sol @@ -89,6 +89,7 @@ abstract contract StrategyModule is Shared { } else if (strategyCached.status == IEulerAggregationVault.StrategyStatus.Active) { $.strategies[_strategy].status = IEulerAggregationVault.StrategyStatus.Emergency; + // we should deduct loss before decrease totalAllocated to not underflow _deductLoss(strategyCached.allocated); $.totalAllocationPoints -= strategyCached.allocationPoints; From 4ee8e240034e7f7ec8e93a88fd74b1cb591d94e4 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 26 Jul 2024 13:02:53 +0300 Subject: [PATCH 295/316] lint --- src/core/common/Shared.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/common/Shared.sol b/src/core/common/Shared.sol index e92a899c..b34e8a89 100644 --- a/src/core/common/Shared.sol +++ b/src/core/common/Shared.sol @@ -49,7 +49,7 @@ abstract contract Shared { } /// @dev Deduct _lossAmount from not-distributed yet amounts, if not enough, socialize loss. - /// @dev not distributed amount is amount available to gulp + interest left. + /// @dev not distributed amount is amount available to gulp + interest left. /// @param _lossAmount Amount lost. function _deductLoss(uint256 _lossAmount) internal { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); From 44b7b47cbaeb6f0dd6c8a37ee3b73ec916de1781 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 26 Jul 2024 13:20:24 +0300 Subject: [PATCH 296/316] clean --- src/core/common/Shared.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/common/Shared.sol b/src/core/common/Shared.sol index b34e8a89..80e1513d 100644 --- a/src/core/common/Shared.sol +++ b/src/core/common/Shared.sol @@ -48,7 +48,7 @@ abstract contract Shared { $.locked = REENTRANCYLOCK__UNLOCKED; } - /// @dev Deduct _lossAmount from not-distributed yet amounts, if not enough, socialize loss. + /// @dev Deduct _lossAmount from not-distributed amount, if not enough, socialize loss. /// @dev not distributed amount is amount available to gulp + interest left. /// @param _lossAmount Amount lost. function _deductLoss(uint256 _lossAmount) internal { From 9a6c46074c59e757df2ddac0274d4fc8889cd3f8 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 26 Jul 2024 13:42:07 +0300 Subject: [PATCH 297/316] clean --- src/core/common/Shared.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/common/Shared.sol b/src/core/common/Shared.sol index 80e1513d..4b39f2f5 100644 --- a/src/core/common/Shared.sol +++ b/src/core/common/Shared.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {Test, console2, stdError} from "forge-std/Test.sol"; - // interfaces import {IHookTarget} from "evk/src/interfaces/IHookTarget.sol"; import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; From fcade528781c4bf5eb98c6a22c6d81b730dee761 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:37:30 +0400 Subject: [PATCH 298/316] Feat: mint fee shares --- src/core/EulerAggregationVault.sol | 30 +++++++++++++----------------- src/core/lib/EventsLib.sol | 2 +- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 61da91bc..3b2ee206 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -432,6 +432,8 @@ contract EulerAggregationVault is // we should deduct loss before decrease totalAllocated to not underflow if (totalLoss > totalYield) { _deductLoss(totalLoss - totalYield); + } else if (totalLoss < totalYield) { + _accruePerformanceFee(totalYield - totalLoss); } $.totalAllocated = $.totalAllocated + totalYield - totalLoss; @@ -456,51 +458,45 @@ contract EulerAggregationVault is ) return (0, 0); uint256 underlyingBalance = IERC4626(_strategy).maxWithdraw(address(this)); + $.strategies[_strategy].allocated = uint120(underlyingBalance); + uint256 yield; uint256 loss; if (underlyingBalance == strategyAllocatedAmount) { return (yield, loss); } else if (underlyingBalance > strategyAllocatedAmount) { yield = underlyingBalance - strategyAllocatedAmount; - uint120 accruedPerformanceFee = _accruePerformanceFee(_strategy, yield); - - if (accruedPerformanceFee > 0) { - underlyingBalance -= accruedPerformanceFee; - yield -= accruedPerformanceFee; - } } else { loss = strategyAllocatedAmount - underlyingBalance; } - $.strategies[_strategy].allocated = uint120(underlyingBalance); - emit Events.ExecuteHarvest(_strategy, underlyingBalance, strategyAllocatedAmount); return (yield, loss); } /// @dev Accrue performace fee on harvested yield. - /// @param _strategy Strategy that the yield is harvested from. /// @param _yield Amount of yield harvested. - /// @return feeAssets Amount of performance fee taken. - function _accruePerformanceFee(address _strategy, uint256 _yield) internal returns (uint120) { + function _accruePerformanceFee(uint256 _yield) internal { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); address cachedFeeRecipient = $.feeRecipient; uint256 cachedPerformanceFee = $.performanceFee; - if (cachedFeeRecipient == address(0) || cachedPerformanceFee == 0) return 0; + if (cachedFeeRecipient == address(0) || cachedPerformanceFee == 0) return; // `feeAssets` will be rounded down to 0 if `yield * performanceFee < 1e18`. uint256 feeAssets = Math.mulDiv(_yield, cachedPerformanceFee, 1e18, Math.Rounding.Floor); + uint256 feeShares = _convertToShares(feeAssets, Math.Rounding.Floor); - if (feeAssets > 0) { - IERC4626(_strategy).withdraw(feeAssets, cachedFeeRecipient, address(this)); - } + if (feeShares != 0) { + // Move feeAssets from gulpable amount to totalAssetsDeposited to not delute other depositors. + $.totalAssetsDeposited += feeAssets; - emit Events.AccruePerformanceFee(cachedFeeRecipient, _yield, feeAssets); + _mint(cachedFeeRecipient, feeShares); + } - return feeAssets.toUint120(); + emit Events.AccruePerformanceFee(cachedFeeRecipient, _yield, feeShares); } /// @dev Override _afterTokenTransfer hook to call IBalanceTracker.balanceTrackerHook() diff --git a/src/core/lib/EventsLib.sol b/src/core/lib/EventsLib.sol index 43844c8f..13dccc26 100644 --- a/src/core/lib/EventsLib.sol +++ b/src/core/lib/EventsLib.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; library EventsLib { /// @dev EulerAggregationVault events event Gulp(uint256 interestLeft, uint256 interestSmearEnd); - event AccruePerformanceFee(address indexed feeRecipient, uint256 yield, uint256 feeAssets); + event AccruePerformanceFee(address indexed feeRecipient, uint256 yield, uint256 feeShares); event Rebalance(address indexed strategy, uint256 amountToRebalance, bool isDeposit); event ExecuteHarvest(address indexed strategy, uint256 strategyBalanceAmount, uint256 strategyAllocatedAmount); event Harvest(uint256 totalAllocated, uint256 totlaYield, uint256 totalLoss); From 5e3f1bdf18a254776acf4b900eda274ad1a42db4 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 30 Jul 2024 16:41:41 +0400 Subject: [PATCH 299/316] fix test: --- test/e2e/PerformanceFeeE2ETest.t.sol | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 4f169ebd..4736e50e 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -106,7 +106,8 @@ contract PerformanceFeeE2ETest is EulerAggregationVaultBase { } (, uint256 performanceFee) = eulerAggregationVault.performanceFeeConfig(); - uint256 expectedPerformanceFee = yield * performanceFee / 1e18; + uint256 expectedPerformanceFeeAssets = yield * performanceFee / 1e18; + uint256 expectedPerformanceFeeShares = eulerAggregationVault.previewDeposit(expectedPerformanceFeeAssets); IEulerAggregationVault.Strategy memory strategyBeforeHarvest = eulerAggregationVault.getStrategy(address(eTST)); uint256 totalAllocatedBefore = eulerAggregationVault.totalAllocated(); @@ -115,13 +116,11 @@ contract PerformanceFeeE2ETest is EulerAggregationVaultBase { vm.prank(user1); eulerAggregationVault.harvest(); - assertGt(expectedPerformanceFee, 0); - assertEq(assetTST.balanceOf(feeRecipient), expectedPerformanceFee); - assertEq( - eulerAggregationVault.getStrategy(address(eTST)).allocated, - strategyBeforeHarvest.allocated + yield - expectedPerformanceFee - ); - assertEq(eulerAggregationVault.totalAllocated(), totalAllocatedBefore + yield - expectedPerformanceFee); + assertGt(expectedPerformanceFeeShares, 0); + assertEq(assetTST.balanceOf(feeRecipient), 0); + assertEq(eulerAggregationVault.balanceOf(feeRecipient), expectedPerformanceFeeShares); + assertEq(eulerAggregationVault.getStrategy(address(eTST)).allocated, strategyBeforeHarvest.allocated + yield); + assertEq(eulerAggregationVault.totalAllocated(), totalAllocatedBefore + yield); // full withdraw, will have to withdraw from strategy as cash reserve is not enough { @@ -141,13 +140,15 @@ contract PerformanceFeeE2ETest is EulerAggregationVaultBase { assertApproxEqAbs(assetTST.balanceOf(user1), user1AssetTSTBalanceBefore + expectedAssetTST, 1); } - // full withdraw of recipient fees + // full redemption of recipient fees { uint256 totalAssetsDepositedBefore = eulerAggregationVault.totalAssetsDeposited(); uint256 assetTSTBalanceBefore = assetTST.balanceOf(feeRecipient); uint256 feeShares = eulerAggregationVault.balanceOf(feeRecipient); uint256 expectedAssets = eulerAggregationVault.convertToAssets(feeShares); + assertGt(expectedAssets, 0); + vm.prank(feeRecipient); eulerAggregationVault.redeem(feeShares, feeRecipient, feeRecipient); From 7c26e9862cec3b657a89949e299885c5ade4bc69 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:38:37 +0400 Subject: [PATCH 300/316] fix invariants --- .../EulerAggregationLayerInvariants.t.sol | 4 +- .../handler/EulerAggregationVaultHandler.sol | 37 +++++++++++++------ test/invariant/util/Actor.sol | 6 +++ 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/test/invariant/EulerAggregationLayerInvariants.t.sol b/test/invariant/EulerAggregationLayerInvariants.t.sol index 00ffb51f..bebcd39b 100644 --- a/test/invariant/EulerAggregationLayerInvariants.t.sol +++ b/test/invariant/EulerAggregationLayerInvariants.t.sol @@ -89,7 +89,7 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { } } - // assertEq(eulerAggregationVault.totalAllocationPoints(), expectedTotalAllocationpoints); + assertEq(eulerAggregationVault.totalAllocationPoints(), expectedTotalAllocationpoints); } // (1) If withdrawal queue length == 0, then the total allocation points should be equal cash reserve allocation points. @@ -143,7 +143,7 @@ contract EulerAggregationVaultInvariants is EulerAggregationVaultBase { address feeRecipient = eulerAggregationVaultHandler.ghost_feeRecipients(i); assertEq( - assetTST.balanceOf(feeRecipient), + eulerAggregationVault.balanceOf(feeRecipient), eulerAggregationVaultHandler.ghost_accumulatedPerformanceFeePerRecipient(feeRecipient) ); } diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index 4f4e0941..4725cdf1 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -62,6 +62,7 @@ contract EulerAggregationVaultHandler is Test { if (success) { ghost_feeRecipients.push(feeRecipientAddr); + actorUtil.setAsFeeReceiver(feeRecipientAddr); } (address feeRecipient,) = eulerAggVault.performanceFeeConfig(); assertEq(feeRecipient, feeRecipientAddr); @@ -124,7 +125,8 @@ contract EulerAggregationVaultHandler is Test { // simulating loss when switching strategy to emergency status IEulerAggregationVault.Strategy memory strategyBefore = eulerAggVault.getStrategy(strategyAddr); - uint256 cached_ghost_totalAssetsDeposited = _simulateLossSocialization(strategyBefore.allocated); + uint256 cached_ghost_totalAssetsDeposited = + _simulateLossSocialization(ghost_totalAssetsDeposited, strategyBefore.allocated); (currentActor, success, returnData) = actorUtil.initiateExactActorCall( 0, @@ -218,6 +220,7 @@ contract EulerAggregationVaultHandler is Test { // check if performance fee is on; store received fee per recipient if call is succesfull (address feeRecipient, uint256 performanceFee) = eulerAggVault.performanceFeeConfig(); uint256 accumulatedPerformanceFee; + uint256 cached_ghost_totalAssetsDeposited = ghost_totalAssetsDeposited; if (feeRecipient != address(0) && performanceFee > 0) { accumulatedPerformanceFee = ghost_accumulatedPerformanceFeePerRecipient[feeRecipient]; address[] memory withdrawalQueueArray = eulerAggVault.withdrawalQueue(); @@ -226,17 +229,22 @@ contract EulerAggregationVaultHandler is Test { uint256 allocated = (eulerAggVault.getStrategy(withdrawalQueueArray[i])).allocated; uint256 underlying = IERC4626(withdrawalQueueArray[i]).maxWithdraw(address(eulerAggVault)); if (underlying >= allocated) { - uint256 yield = underlying - allocated; - uint256 performancefee = Math.mulDiv(yield, performanceFee, 1e18, Math.Rounding.Floor); - accumulatedPerformanceFee += performancefee; - totalYield += yield - performancefee; + totalYield += underlying - allocated; } else { totalLoss += allocated - underlying; } } - } - uint256 cached_ghost_totalAssetsDeposited = _simulateLossSocialization(totalLoss); + if (totalYield > totalLoss) { + uint256 performancefeeAssets = Math.mulDiv(totalYield, performanceFee, 1e18, Math.Rounding.Floor); + + accumulatedPerformanceFee = eulerAggVault.previewDeposit(performancefeeAssets); + cached_ghost_totalAssetsDeposited += performancefeeAssets; + } else if (totalYield < totalLoss) { + cached_ghost_totalAssetsDeposited = + _simulateLossSocialization(cached_ghost_totalAssetsDeposited, totalLoss); + } + } // simulate loss (, success, returnData) = actorUtil.initiateActorCall( @@ -272,6 +280,7 @@ contract EulerAggregationVaultHandler is Test { function deposit(uint256 _actorIndexSeed, uint256 _assets, address _receiver) external { vm.assume(_receiver != address(0)); + vm.assume(!actorUtil.isFeeReceiver(_receiver)); (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); @@ -295,6 +304,7 @@ contract EulerAggregationVaultHandler is Test { function mint(uint256 _actorIndexSeed, uint256 _shares, address _receiver) external { vm.assume(_receiver != address(0)); + vm.assume(!actorUtil.isFeeReceiver(_receiver)); (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); @@ -319,6 +329,7 @@ contract EulerAggregationVaultHandler is Test { function withdraw(uint256 _actorIndexSeed, uint256 _assets, address _receiver) external { vm.assume(_receiver != address(0)); + vm.assume(!actorUtil.isFeeReceiver(_receiver)); (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); @@ -336,6 +347,7 @@ contract EulerAggregationVaultHandler is Test { function redeem(uint256 _actorIndexSeed, uint256 _shares, address _receiver) external { vm.assume(_receiver != address(0)); + vm.assume(!actorUtil.isFeeReceiver(_receiver)); (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); @@ -369,13 +381,16 @@ contract EulerAggregationVaultHandler is Test { // This function simulate loss socialization, the part related to decreasing totalAssetsDeposited only. // Return the expected ghost_totalAssetsDeposited after loss socialization. - function _simulateLossSocialization(uint256 _loss) private view returns (uint256) { - uint256 cached_ghost_totalAssetsDeposited = ghost_totalAssetsDeposited; + function _simulateLossSocialization(uint256 cachedGhostTotalAssetsDeposited, uint256 _loss) + private + view + returns (uint256) + { IEulerAggregationVault.AggregationVaultSavingRate memory avsr = eulerAggVault.getAggregationVaultSavingRate(); if (_loss > avsr.interestLeft) { - cached_ghost_totalAssetsDeposited -= _loss - avsr.interestLeft; + cachedGhostTotalAssetsDeposited -= _loss - avsr.interestLeft; } - return cached_ghost_totalAssetsDeposited; + return cachedGhostTotalAssetsDeposited; } } diff --git a/test/invariant/util/Actor.sol b/test/invariant/util/Actor.sol index bfc16ef1..02a45b61 100644 --- a/test/invariant/util/Actor.sol +++ b/test/invariant/util/Actor.sol @@ -9,6 +9,8 @@ contract Actor is Test { /// @dev actor[0] will always be a manager address that have access to all EulerAggregationVault roles. address[] public actors; + /// @dev using a mapping to detect fee receiver, just for cleaner and expected test results. + mapping(address => bool) public isFeeReceiver; constructor(address _eulerAggregationVault) { eulerAggregationVault = _eulerAggregationVault; @@ -45,6 +47,10 @@ contract Actor is Test { return (currentActor, success, returnData); } + function setAsFeeReceiver(address _feeReceiver) external { + isFeeReceiver[_feeReceiver] = true; + } + function fetchActor(uint256 _actorIndexSeed) external view returns (address, uint256) { uint256 randomActorIndex = bound(_actorIndexSeed, 0, actors.length - 1); address randomActor = actors[randomActorIndex]; From 5c05f3dd1118e6b2c7540978c370e2add5fde86f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 30 Jul 2024 21:01:32 +0400 Subject: [PATCH 301/316] fix --- test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol index 83eeb52a..fd69ac15 100644 --- a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol +++ b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol @@ -85,22 +85,22 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { function testFuzzRedeem( address _receiver, uint256 _sharesToMint, - uint256 _sharesToRedeem, - uint256 _timestampAfterDeposit + uint256 _sharesToRedeem ) public { vm.assume(_receiver != address(0)); _sharesToMint = bound(_sharesToMint, 1, type(uint256).max - 1); - _timestampAfterDeposit = bound(_timestampAfterDeposit, 0, 86400); // deposit uint256 assetsToDeposit = eulerAggregationVault.previewMint(_sharesToMint); if (assetsToDeposit > MAX_ALLOWED) assetsToDeposit = MAX_ALLOWED; assetTST.mint(user1, assetsToDeposit); + + _sharesToMint = eulerAggregationVault.previewDeposit(assetsToDeposit); _sharesToRedeem = bound(_sharesToRedeem, 0, _sharesToMint); _mint(user1, assetsToDeposit, _sharesToMint); - vm.warp(block.timestamp + _timestampAfterDeposit); + vm.warp(block.timestamp + 86400); // fuzz partial & full redeem uint256 balanceBefore = eulerAggregationVault.balanceOf(user1); @@ -112,10 +112,10 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { uint256 assetsToWithdraw = eulerAggregationVault.redeem(_sharesToRedeem, _receiver, user1); vm.stopPrank(); - assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _sharesToRedeem); - assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _sharesToRedeem); - assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); - assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + assetsToWithdraw); + // assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _sharesToRedeem); + // assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _sharesToRedeem); + // assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); + // assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + assetsToWithdraw); } function _deposit(address _from, uint256 _assets) private { From b8f1162e948cc2c95ebc00656ce1e26bcdb4dee7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 30 Jul 2024 21:03:46 +0400 Subject: [PATCH 302/316] fix --- test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol index fd69ac15..5f79a5cc 100644 --- a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol +++ b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol @@ -56,10 +56,10 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { eulerAggregationVault.withdraw(_assetsToWithdraw, _receiver, user1); vm.stopPrank(); - assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _assetsToWithdraw); - assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _assetsToWithdraw); - assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); - assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + _assetsToWithdraw); + // assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _assetsToWithdraw); + // assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _assetsToWithdraw); + // assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); + // assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + _assetsToWithdraw); } function testFuzzMint(uint256 _shares) public { @@ -82,11 +82,7 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { assertEq(assetTST.balanceOf(user1), userAssetBalanceBefore - assets); } - function testFuzzRedeem( - address _receiver, - uint256 _sharesToMint, - uint256 _sharesToRedeem - ) public { + function testFuzzRedeem(address _receiver, uint256 _sharesToMint, uint256 _sharesToRedeem) public { vm.assume(_receiver != address(0)); _sharesToMint = bound(_sharesToMint, 1, type(uint256).max - 1); @@ -96,7 +92,6 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { if (assetsToDeposit > MAX_ALLOWED) assetsToDeposit = MAX_ALLOWED; assetTST.mint(user1, assetsToDeposit); - _sharesToMint = eulerAggregationVault.previewDeposit(assetsToDeposit); _sharesToRedeem = bound(_sharesToRedeem, 0, _sharesToMint); _mint(user1, assetsToDeposit, _sharesToMint); From 12375225341d8a7b1756536bf2277aa2c2d43ec7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 30 Jul 2024 22:31:27 +0400 Subject: [PATCH 303/316] fix --- test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol index 5f79a5cc..8fec6338 100644 --- a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol +++ b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol @@ -56,7 +56,7 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { eulerAggregationVault.withdraw(_assetsToWithdraw, _receiver, user1); vm.stopPrank(); - // assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _assetsToWithdraw); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _assetsToWithdraw); // assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _assetsToWithdraw); // assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); // assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + _assetsToWithdraw); @@ -107,7 +107,7 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { uint256 assetsToWithdraw = eulerAggregationVault.redeem(_sharesToRedeem, _receiver, user1); vm.stopPrank(); - // assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _sharesToRedeem); + assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _sharesToRedeem); // assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _sharesToRedeem); // assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); // assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + assetsToWithdraw); From a50bfef20a40ac69a1fc992ab055658013e9370d Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 31 Jul 2024 00:47:34 +0400 Subject: [PATCH 304/316] fix --- test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol index 8fec6338..50f787db 100644 --- a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol +++ b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol @@ -57,7 +57,7 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { vm.stopPrank(); assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _assetsToWithdraw); - // assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _assetsToWithdraw); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _assetsToWithdraw); // assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); // assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + _assetsToWithdraw); } @@ -108,7 +108,7 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { vm.stopPrank(); assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _sharesToRedeem); - // assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _sharesToRedeem); + assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _sharesToRedeem); // assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); // assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + assetsToWithdraw); } From 7751a08d8184c2cf047d91f0582a0f4b648759e1 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 31 Jul 2024 01:13:22 +0400 Subject: [PATCH 305/316] fix --- test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol index 50f787db..cf2fdbef 100644 --- a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol +++ b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol @@ -58,7 +58,7 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _assetsToWithdraw); assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _assetsToWithdraw); - // assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); // assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + _assetsToWithdraw); } @@ -109,7 +109,7 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _sharesToRedeem); assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _sharesToRedeem); - // assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); + assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); // assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + assetsToWithdraw); } From 70412929ad7d0e57be2c19e0aec46b13784b73e7 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 31 Jul 2024 01:25:33 +0400 Subject: [PATCH 306/316] fix --- test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol index cf2fdbef..739f79b4 100644 --- a/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol +++ b/test/fuzz/DepositWithdrawMintBurnFuzzTest.t.sol @@ -59,7 +59,7 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _assetsToWithdraw); assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _assetsToWithdraw); assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - _assetsToWithdraw); - // assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + _assetsToWithdraw); + assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + _assetsToWithdraw); } function testFuzzMint(uint256 _shares) public { @@ -110,7 +110,7 @@ contract DepositWithdrawMintBurnFuzzTest is EulerAggregationVaultBase { assertEq(eulerAggregationVault.balanceOf(user1), balanceBefore - _sharesToRedeem); assertEq(eulerAggregationVault.totalSupply(), totalSupplyBefore - _sharesToRedeem); assertEq(eulerAggregationVault.totalAssetsDeposited(), totalAssetsDepositedBefore - assetsToWithdraw); - // assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + assetsToWithdraw); + assertEq(assetTST.balanceOf(_receiver), receiverAssetBalanceBefore + assetsToWithdraw); } function _deposit(address _from, uint256 _assets) private { From 25ea7d027ae66565d280e8b09748e4b20a282cee Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 31 Jul 2024 15:26:18 +0400 Subject: [PATCH 307/316] chore: commentS --- src/core/EulerAggregationVault.sol | 41 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 3b2ee206..4dfe7e03 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -201,7 +201,7 @@ contract EulerAggregationVault is /// @notice Harvest all the strategies. Any positive yiled should be gupled by calling gulp() after harvesting. /// @dev This function will loop through the strategies following the withdrawal queue order and harvest all. - /// @dev Harvest yield and losses will be aggregated and only net yield/loss will be accounted. + /// @dev Harvest positive and negative yields will be aggregated and only net amounts will be accounted. function harvest() external nonReentrant { _updateInterestAccrued(); @@ -213,7 +213,7 @@ contract EulerAggregationVault is return _updateInterestAccrued(); } - /// @notice gulp positive harvest yield + /// @notice gulp harvested positive yield function gulp() external nonReentrant { _gulp(); } @@ -415,37 +415,38 @@ contract EulerAggregationVault is _gulp(); } - /// @dev Loop through stratgies, aggregate positive yield and loss and account for net amount. + /// @dev Loop through stratgies, aggregate positive and negative yield and account for net amounts. /// @dev Loss socialization will be taken out from interest left first, if not enough, socialize on deposits. + /// @dev Performance fee will only be applied on net positive yield. function _harvest() internal { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); - uint256 totalYield; - uint256 totalLoss; + uint256 totalPositiveYield; + uint256 totalNegativeYield; for (uint256 i; i < $.withdrawalQueue.length; ++i) { - (uint256 yield, uint256 loss) = _executeHarvest($.withdrawalQueue[i]); + (uint256 positiveYield, uint256 loss) = _executeHarvest($.withdrawalQueue[i]); - totalYield += yield; - totalLoss += loss; + totalPositiveYield += positiveYield; + totalNegativeYield += loss; } // we should deduct loss before decrease totalAllocated to not underflow - if (totalLoss > totalYield) { - _deductLoss(totalLoss - totalYield); - } else if (totalLoss < totalYield) { - _accruePerformanceFee(totalYield - totalLoss); + if (totalNegativeYield > totalPositiveYield) { + _deductLoss(totalNegativeYield - totalPositiveYield); + } else if (totalNegativeYield < totalPositiveYield) { + _accruePerformanceFee(totalPositiveYield - totalNegativeYield); } - $.totalAllocated = $.totalAllocated + totalYield - totalLoss; + $.totalAllocated = $.totalAllocated + totalPositiveYield - totalNegativeYield; _gulp(); - emit Events.Harvest($.totalAllocated, totalYield, totalLoss); + emit Events.Harvest($.totalAllocated, totalPositiveYield, totalNegativeYield); } /// @dev Execute harvest on a single strategy. /// @param _strategy Strategy address. - /// @return yield Amount of yield if any, else 0. + /// @return positiveYield Amount of positive yield if any, else 0. /// @return loss Amount of loss if any, else 0. function _executeHarvest(address _strategy) internal returns (uint256, uint256) { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); @@ -460,23 +461,23 @@ contract EulerAggregationVault is uint256 underlyingBalance = IERC4626(_strategy).maxWithdraw(address(this)); $.strategies[_strategy].allocated = uint120(underlyingBalance); - uint256 yield; + uint256 positiveYield; uint256 loss; if (underlyingBalance == strategyAllocatedAmount) { - return (yield, loss); + return (positiveYield, loss); } else if (underlyingBalance > strategyAllocatedAmount) { - yield = underlyingBalance - strategyAllocatedAmount; + positiveYield = underlyingBalance - strategyAllocatedAmount; } else { loss = strategyAllocatedAmount - underlyingBalance; } emit Events.ExecuteHarvest(_strategy, underlyingBalance, strategyAllocatedAmount); - return (yield, loss); + return (positiveYield, loss); } /// @dev Accrue performace fee on harvested yield. - /// @param _yield Amount of yield harvested. + /// @param _yield Net positive yield. function _accruePerformanceFee(uint256 _yield) internal { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); From 53de3ac0e1ff607b57ba6d418ff91be70d1b968f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Wed, 31 Jul 2024 16:34:42 +0400 Subject: [PATCH 308/316] Change performanceFee to uint96 --- src/core/EulerAggregationVault.sol | 8 +++++--- src/core/lib/EventsLib.sol | 2 +- src/core/lib/StorageLib.sol | 2 +- src/core/module/Fee.sol | 6 +++--- test/e2e/PerformanceFeeE2ETest.t.sol | 4 ++-- test/invariant/handler/EulerAggregationVaultHandler.sol | 4 ++-- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 4dfe7e03..0ba23265 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -102,7 +102,7 @@ contract EulerAggregationVault is {} /// @dev See {FeeModule-setPerformanceFee}. - function setPerformanceFee(uint256 _newFee) external override onlyRole(AGGREGATION_VAULT_MANAGER) use(feeModule) {} + function setPerformanceFee(uint96 _newFee) external override onlyRole(AGGREGATION_VAULT_MANAGER) use(feeModule) {} /// @dev See {RewardsModule-optInStrategyRewards}. function optInStrategyRewards(address _strategy) @@ -273,7 +273,7 @@ contract EulerAggregationVault is /// @notice Get the performance fee config. /// @return adddress Fee recipient. /// @return uint256 Fee percentage. - function performanceFeeConfig() external view virtual returns (address, uint256) { + function performanceFeeConfig() external view virtual returns (address, uint96) { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); return ($.feeRecipient, $.performanceFee); @@ -415,6 +415,8 @@ contract EulerAggregationVault is _gulp(); } + function checkHarvest() internal {} + /// @dev Loop through stratgies, aggregate positive and negative yield and account for net amounts. /// @dev Loss socialization will be taken out from interest left first, if not enough, socialize on deposits. /// @dev Performance fee will only be applied on net positive yield. @@ -482,7 +484,7 @@ contract EulerAggregationVault is AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); address cachedFeeRecipient = $.feeRecipient; - uint256 cachedPerformanceFee = $.performanceFee; + uint96 cachedPerformanceFee = $.performanceFee; if (cachedFeeRecipient == address(0) || cachedPerformanceFee == 0) return; diff --git a/src/core/lib/EventsLib.sol b/src/core/lib/EventsLib.sol index 13dccc26..83d86070 100644 --- a/src/core/lib/EventsLib.sol +++ b/src/core/lib/EventsLib.sol @@ -19,7 +19,7 @@ library EventsLib { /// @dev Fee events event SetFeeRecipient(address indexed oldRecipient, address indexed newRecipient); - event SetPerformanceFee(uint256 oldFee, uint256 newFee); + event SetPerformanceFee(uint96 oldFee, uint96 newFee); /// @dev Hooks events event SetHooksConfig(address indexed hooksTarget, uint32 hookedFns); diff --git a/src/core/lib/StorageLib.sol b/src/core/lib/StorageLib.sol index 804d952f..866f87a9 100644 --- a/src/core/lib/StorageLib.sol +++ b/src/core/lib/StorageLib.sol @@ -13,7 +13,7 @@ struct AggregationVaultStorage { /// Total amount of allocation points across all strategies including the cash reserve. uint256 totalAllocationPoints; /// fee rate - uint256 performanceFee; + uint96 performanceFee; /// fee recipient address address feeRecipient; /// Mapping between a strategy address and it's allocation config diff --git a/src/core/module/Fee.sol b/src/core/module/Fee.sol index 3f1c3b73..902f3b05 100644 --- a/src/core/module/Fee.sol +++ b/src/core/module/Fee.sol @@ -11,7 +11,7 @@ import {EventsLib as Events} from "../lib/EventsLib.sol"; /// @author Euler Labs (https://www.eulerlabs.com/) abstract contract FeeModule { /// @dev The maximum performanceFee the vault can have is 50% - uint256 internal constant MAX_PERFORMANCE_FEE = 0.5e18; + uint96 internal constant MAX_PERFORMANCE_FEE = 0.5e18; /// @notice Set performance fee recipient address /// @param _newFeeRecipient Recipient address @@ -25,10 +25,10 @@ abstract contract FeeModule { /// @notice Set performance fee (1e18 == 100%) /// @param _newFee Fee rate - function setPerformanceFee(uint256 _newFee) external virtual { + function setPerformanceFee(uint96 _newFee) external virtual { AggregationVaultStorage storage $ = StorageLib._getAggregationVaultStorage(); - uint256 performanceFeeCached = $.performanceFee; + uint96 performanceFeeCached = $.performanceFee; if (_newFee > MAX_PERFORMANCE_FEE) revert Errors.MaxPerformanceFeeExceeded(); if ($.feeRecipient == address(0)) revert Errors.FeeRecipientNotSet(); diff --git a/test/e2e/PerformanceFeeE2ETest.t.sol b/test/e2e/PerformanceFeeE2ETest.t.sol index 4736e50e..4395f2b8 100644 --- a/test/e2e/PerformanceFeeE2ETest.t.sol +++ b/test/e2e/PerformanceFeeE2ETest.t.sol @@ -33,7 +33,7 @@ contract PerformanceFeeE2ETest is EulerAggregationVaultBase { assertEq(fee, 0); assertEq(feeRecipientAddr, address(0)); - uint256 newPerformanceFee = 3e17; + uint96 newPerformanceFee = 3e17; vm.startPrank(manager); eulerAggregationVault.setFeeRecipient(feeRecipient); @@ -46,7 +46,7 @@ contract PerformanceFeeE2ETest is EulerAggregationVaultBase { } function testHarvestWithFeeEnabled() public { - uint256 newPerformanceFee = 3e17; + uint96 newPerformanceFee = 3e17; vm.startPrank(manager); eulerAggregationVault.setFeeRecipient(feeRecipient); diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index 4725cdf1..8bdc576b 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -68,12 +68,12 @@ contract EulerAggregationVaultHandler is Test { assertEq(feeRecipient, feeRecipientAddr); } - function setPerformanceFee(uint256 _newFee) external { + function setPerformanceFee(uint96 _newFee) external { (currentActor, success, returnData) = actorUtil.initiateExactActorCall( 0, address(eulerAggVault), abi.encodeWithSelector(EulerAggregationVault.setPerformanceFee.selector, _newFee) ); - (, uint256 fee) = eulerAggVault.performanceFeeConfig(); + (, uint96 fee) = eulerAggVault.performanceFeeConfig(); assertEq(_newFee, fee); } From a0c696fd2f5a2377b8b22e9cb58a7c296f386d66 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:00:10 +0400 Subject: [PATCH 309/316] feat: harvest cooldown --- src/core/EulerAggregationVault.sol | 19 ++++++++++++------- src/core/lib/StorageLib.sol | 3 ++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 0ba23265..e8643d4b 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -51,6 +51,8 @@ contract EulerAggregationVault is bytes32 public constant WITHDRAWAL_QUEUE_MANAGER = keccak256("WITHDRAWAL_QUEUE_MANAGER"); bytes32 public constant WITHDRAWAL_QUEUE_MANAGER_ADMIN = keccak256("WITHDRAWAL_QUEUE_MANAGER_ADMIN"); + uint256 public constant HARVEST_COOLDOWN = 1 days; + /// @dev Constructor. constructor(ConstructorParams memory _constructorParams) Dispatch( @@ -205,7 +207,7 @@ contract EulerAggregationVault is function harvest() external nonReentrant { _updateInterestAccrued(); - _harvest(); + _harvest(false); } /// @notice update accrued interest @@ -316,7 +318,7 @@ contract EulerAggregationVault is _callHooksTarget(WITHDRAW, _msgSender()); - _harvest(); + _harvest(true); return super.withdraw(_assets, _receiver, _owner); } @@ -334,7 +336,7 @@ contract EulerAggregationVault is _callHooksTarget(REDEEM, _msgSender()); - _harvest(); + _harvest(true); return super.redeem(_shares, _receiver, _owner); } @@ -415,14 +417,16 @@ contract EulerAggregationVault is _gulp(); } - function checkHarvest() internal {} - /// @dev Loop through stratgies, aggregate positive and negative yield and account for net amounts. /// @dev Loss socialization will be taken out from interest left first, if not enough, socialize on deposits. /// @dev Performance fee will only be applied on net positive yield. - function _harvest() internal { + function _harvest(bool _checkCooldown) internal { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + if ((_checkCooldown) && ($.lastHarvestTimestamp + HARVEST_COOLDOWN >= block.timestamp)) { + return; + } + uint256 totalPositiveYield; uint256 totalNegativeYield; for (uint256 i; i < $.withdrawalQueue.length; ++i) { @@ -432,7 +436,7 @@ contract EulerAggregationVault is totalNegativeYield += loss; } - // we should deduct loss before decrease totalAllocated to not underflow + // we should deduct loss before updating totalAllocated to not underflow if (totalNegativeYield > totalPositiveYield) { _deductLoss(totalNegativeYield - totalPositiveYield); } else if (totalNegativeYield < totalPositiveYield) { @@ -440,6 +444,7 @@ contract EulerAggregationVault is } $.totalAllocated = $.totalAllocated + totalPositiveYield - totalNegativeYield; + $.lastHarvestTimestamp = block.timestamp; _gulp(); diff --git a/src/core/lib/StorageLib.sol b/src/core/lib/StorageLib.sol index 866f87a9..0c10f795 100644 --- a/src/core/lib/StorageLib.sol +++ b/src/core/lib/StorageLib.sol @@ -20,7 +20,8 @@ struct AggregationVaultStorage { mapping(address => IEulerAggregationVault.Strategy) strategies; /// @dev An array of strategy addresses to withdraw from address[] withdrawalQueue; - + /// @dev Last harvest timestamp + uint256 lastHarvestTimestamp; /// lastInterestUpdate: last timestamp where interest was updated. uint40 lastInterestUpdate; From f4c2cafd5b76ffe4b016789ea89c4a8d10a63f3f Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:04:21 +0400 Subject: [PATCH 310/316] remove gulp after withdraW --- src/core/EulerAggregationVault.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index e8643d4b..16871977 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -413,8 +413,6 @@ contract EulerAggregationVault is $.totalAssetsDeposited -= _assets; super._withdraw(_caller, _receiver, _owner, _assets, _shares); - - _gulp(); } /// @dev Loop through stratgies, aggregate positive and negative yield and account for net amounts. From e660347371b25052e8bb2ce9031875bdc110508e Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 2 Aug 2024 00:12:11 +0400 Subject: [PATCH 311/316] improve invariants --- src/core/EulerAggregationVault.sol | 16 ++-- .../handler/EulerAggregationVaultHandler.sol | 84 +++++++++++-------- 2 files changed, 62 insertions(+), 38 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 16871977..00963065 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -41,6 +41,8 @@ contract EulerAggregationVault is using SafeERC20 for IERC20; using SafeCast for uint256; + uint256 public constant HARVEST_COOLDOWN = 1 days; + // Roles bytes32 public constant GUARDIAN = keccak256("GUARDIAN"); bytes32 public constant GUARDIAN_ADMIN = keccak256("GUARDIAN_ADMIN"); @@ -51,8 +53,6 @@ contract EulerAggregationVault is bytes32 public constant WITHDRAWAL_QUEUE_MANAGER = keccak256("WITHDRAWAL_QUEUE_MANAGER"); bytes32 public constant WITHDRAWAL_QUEUE_MANAGER_ADMIN = keccak256("WITHDRAWAL_QUEUE_MANAGER_ADMIN"); - uint256 public constant HARVEST_COOLDOWN = 1 days; - /// @dev Constructor. constructor(ConstructorParams memory _constructorParams) Dispatch( @@ -275,20 +275,26 @@ contract EulerAggregationVault is /// @notice Get the performance fee config. /// @return adddress Fee recipient. /// @return uint256 Fee percentage. - function performanceFeeConfig() external view virtual returns (address, uint96) { + function performanceFeeConfig() external view returns (address, uint96) { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); return ($.feeRecipient, $.performanceFee); } - /// @notice Return the withdrawal queue length. + /// @notice Return the withdrawal queue. /// @return uint256 length. - function withdrawalQueue() external view virtual returns (address[] memory) { + function withdrawalQueue() external view returns (address[] memory) { AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); return $.withdrawalQueue; } + function lastHarvestTimestamp() external view returns (uint256) { + AggregationVaultStorage storage $ = Storage._getAggregationVaultStorage(); + + return $.lastHarvestTimestamp; + } + /// @dev See {IERC4626-deposit}. /// @dev Call DEPOSIT hook if enabled. function deposit(uint256 _assets, address _receiver) public override nonReentrant returns (uint256) { diff --git a/test/invariant/handler/EulerAggregationVaultHandler.sol b/test/invariant/handler/EulerAggregationVaultHandler.sol index 8bdc576b..080f8164 100644 --- a/test/invariant/handler/EulerAggregationVaultHandler.sol +++ b/test/invariant/handler/EulerAggregationVaultHandler.sol @@ -214,37 +214,8 @@ contract EulerAggregationVaultHandler is Test { } function harvest(uint256 _actorIndexSeed) external { - // track total yield and total loss to simulate loss socialization - uint256 totalYield; - uint256 totalLoss; - // check if performance fee is on; store received fee per recipient if call is succesfull - (address feeRecipient, uint256 performanceFee) = eulerAggVault.performanceFeeConfig(); - uint256 accumulatedPerformanceFee; - uint256 cached_ghost_totalAssetsDeposited = ghost_totalAssetsDeposited; - if (feeRecipient != address(0) && performanceFee > 0) { - accumulatedPerformanceFee = ghost_accumulatedPerformanceFeePerRecipient[feeRecipient]; - address[] memory withdrawalQueueArray = eulerAggVault.withdrawalQueue(); - - for (uint256 i; i < withdrawalQueueArray.length; i++) { - uint256 allocated = (eulerAggVault.getStrategy(withdrawalQueueArray[i])).allocated; - uint256 underlying = IERC4626(withdrawalQueueArray[i]).maxWithdraw(address(eulerAggVault)); - if (underlying >= allocated) { - totalYield += underlying - allocated; - } else { - totalLoss += allocated - underlying; - } - } - - if (totalYield > totalLoss) { - uint256 performancefeeAssets = Math.mulDiv(totalYield, performanceFee, 1e18, Math.Rounding.Floor); - - accumulatedPerformanceFee = eulerAggVault.previewDeposit(performancefeeAssets); - cached_ghost_totalAssetsDeposited += performancefeeAssets; - } else if (totalYield < totalLoss) { - cached_ghost_totalAssetsDeposited = - _simulateLossSocialization(cached_ghost_totalAssetsDeposited, totalLoss); - } - } + (uint256 expectedAccumulatedPerformanceFee, uint256 expectedTotalAssetsDeposited) = + _simulateFeeAndLossSocialization(); // simulate loss (, success, returnData) = actorUtil.initiateActorCall( @@ -252,8 +223,9 @@ contract EulerAggregationVaultHandler is Test { ); if (success) { - ghost_accumulatedPerformanceFeePerRecipient[feeRecipient] = accumulatedPerformanceFee; - ghost_totalAssetsDeposited = cached_ghost_totalAssetsDeposited; + (address feeRecipient,) = eulerAggVault.performanceFeeConfig(); + ghost_accumulatedPerformanceFeePerRecipient[feeRecipient] = expectedAccumulatedPerformanceFee; + ghost_totalAssetsDeposited = expectedTotalAssetsDeposited; } } @@ -331,6 +303,10 @@ contract EulerAggregationVaultHandler is Test { vm.assume(_receiver != address(0)); vm.assume(!actorUtil.isFeeReceiver(_receiver)); + uint256 previousHarvestTimestamp = eulerAggVault.lastHarvestTimestamp(); + (uint256 expectedAccumulatedPerformanceFee, uint256 expectedTotalAssetsDeposited) = + _simulateFeeAndLossSocialization(); + (currentActor, currentActorIndex) = actorUtil.fetchActor(_actorIndexSeed); (currentActor, success, returnData) = actorUtil.initiateExactActorCall( @@ -340,6 +316,12 @@ contract EulerAggregationVaultHandler is Test { ); if (success) { + if (block.timestamp > previousHarvestTimestamp + eulerAggVault.HARVEST_COOLDOWN()) { + (address feeRecipient,) = eulerAggVault.performanceFeeConfig(); + ghost_accumulatedPerformanceFeePerRecipient[feeRecipient] = expectedAccumulatedPerformanceFee; + ghost_totalAssetsDeposited = expectedTotalAssetsDeposited; + } + ghost_totalAssetsDeposited -= _assets; } assertEq(eulerAggVault.totalAssetsDeposited(), ghost_totalAssetsDeposited); @@ -393,4 +375,40 @@ contract EulerAggregationVaultHandler is Test { return cachedGhostTotalAssetsDeposited; } + + function _simulateFeeAndLossSocialization() private view returns (uint256, uint256) { + // track total yield and total loss to simulate loss socialization + uint256 totalYield; + uint256 totalLoss; + // check if performance fee is on; store received fee per recipient if call is succesfull + (address feeRecipient, uint256 performanceFee) = eulerAggVault.performanceFeeConfig(); + uint256 accumulatedPerformanceFee; + uint256 cached_ghost_totalAssetsDeposited = ghost_totalAssetsDeposited; + if (feeRecipient != address(0) && performanceFee > 0) { + accumulatedPerformanceFee = ghost_accumulatedPerformanceFeePerRecipient[feeRecipient]; + address[] memory withdrawalQueueArray = eulerAggVault.withdrawalQueue(); + + for (uint256 i; i < withdrawalQueueArray.length; i++) { + uint256 allocated = (eulerAggVault.getStrategy(withdrawalQueueArray[i])).allocated; + uint256 underlying = IERC4626(withdrawalQueueArray[i]).maxWithdraw(address(eulerAggVault)); + if (underlying >= allocated) { + totalYield += underlying - allocated; + } else { + totalLoss += allocated - underlying; + } + } + + if (totalYield > totalLoss) { + uint256 performancefeeAssets = Math.mulDiv(totalYield, performanceFee, 1e18, Math.Rounding.Floor); + + accumulatedPerformanceFee = eulerAggVault.previewDeposit(performancefeeAssets); + cached_ghost_totalAssetsDeposited += performancefeeAssets; + } else if (totalYield < totalLoss) { + cached_ghost_totalAssetsDeposited = + _simulateLossSocialization(cached_ghost_totalAssetsDeposited, totalLoss); + } + } + + return (accumulatedPerformanceFee, cached_ghost_totalAssetsDeposited); + } } From 1a5ee6916f3f37dbf31edcdf58ca70c59327eb1e Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:50:56 +0400 Subject: [PATCH 312/316] update test: --- ...positRebalanceHarvestWithdrawE2ETest.t.sol | 103 ++++-------------- 1 file changed, 19 insertions(+), 84 deletions(-) diff --git a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol index 45883872..c401c865 100644 --- a/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol +++ b/test/e2e/DepositRebalanceHarvestWithdrawE2ETest.t.sol @@ -10,7 +10,8 @@ import { IRMTestDefault, TestERC20, WithdrawalQueue, - IEulerAggregationVault + IEulerAggregationVault, + ErrorsLib } from "../common/EulerAggregationVaultBase.t.sol"; contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { @@ -504,21 +505,6 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { } function testWithdraw_NotEnoughAssets() public { - IEVault eTSTsecondary; - { - eTSTsecondary = IEVault( - factory.createProxy( - address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount) - ) - ); - eTSTsecondary.setInterestRateModel(address(new IRMTestDefault())); - eTSTsecondary.setMaxLiquidationDiscount(0.2e4); - eTSTsecondary.setFeeReceiver(feeReceiver); - - uint256 initialStrategyAllocationPoints = 1000e18; - _addStrategy(manager, address(eTSTsecondary), initialStrategyAllocationPoints); - } - uint256 amountToDeposit = 10000e18; // deposit into aggregator @@ -540,98 +526,47 @@ contract DepositRebalanceHarvestWithdrawE2ETest is EulerAggregationVaultBase { } // rebalance into strategy - // 2500 total points; 1000 for reserve(40%), 500(20%) for eTST, 1000(40%) for eTSTsecondary - // 10k deposited; 4000 for reserve, 2000 for eTST, 4000 for eTSTsecondary vm.warp(block.timestamp + 86400); { IEulerAggregationVault.Strategy memory eTSTstrategyBefore = eulerAggregationVault.getStrategy(address(eTST)); - IEulerAggregationVault.Strategy memory eTSTsecondarystrategyBefore = - eulerAggregationVault.getStrategy(address(eTSTsecondary)); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), eTSTstrategyBefore.allocated); - assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), - eTSTsecondarystrategyBefore.allocated - ); uint256 expectedeTSTStrategyCash = eulerAggregationVault.totalAssetsAllocatable() * eTSTstrategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); - uint256 expectedeTSTsecondaryStrategyCash = eulerAggregationVault.totalAssetsAllocatable() - * eTSTsecondarystrategyBefore.allocationPoints / eulerAggregationVault.totalAllocationPoints(); assertTrue(expectedeTSTStrategyCash != 0); - assertTrue(expectedeTSTsecondaryStrategyCash != 0); - address[] memory strategiesToRebalance = new address[](2); + address[] memory strategiesToRebalance = new address[](1); strategiesToRebalance[0] = address(eTST); - strategiesToRebalance[1] = address(eTSTsecondary); vm.prank(user1); eulerAggregationVault.rebalance(strategiesToRebalance); - assertEq( - eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash - ); + assertEq(eulerAggregationVault.totalAllocated(), expectedeTSTStrategyCash); assertEq(eTST.convertToAssets(eTST.balanceOf(address(eulerAggregationVault))), expectedeTSTStrategyCash); - assertEq( - eTSTsecondary.convertToAssets(eTSTsecondary.balanceOf(address(eulerAggregationVault))), - expectedeTSTsecondaryStrategyCash - ); assertEq((eulerAggregationVault.getStrategy(address(eTST))).allocated, expectedeTSTStrategyCash); - assertEq( - (eulerAggregationVault.getStrategy(address(eTSTsecondary))).allocated, expectedeTSTsecondaryStrategyCash - ); - assertEq( - assetTST.balanceOf(address(eulerAggregationVault)), - amountToDeposit - (expectedeTSTStrategyCash + expectedeTSTsecondaryStrategyCash) - ); + assertEq(assetTST.balanceOf(address(eulerAggregationVault)), amountToDeposit - expectedeTSTStrategyCash); } vm.warp(block.timestamp + 86400); - uint256 eTSTYield; - uint256 eTSTsecondaryYield; - { - // mock an increase of aggregator balance due to yield - uint256 aggrCurrenteTSTShareBalance = eTST.balanceOf(address(eulerAggregationVault)); - uint256 aggrCurrenteTSTUnderlyingBalance = eTST.convertToAssets(aggrCurrenteTSTShareBalance); - uint256 aggrCurrenteTSTsecondaryShareBalance = eTSTsecondary.balanceOf(address(eulerAggregationVault)); - uint256 aggrCurrenteTSTsecondaryUnderlyingBalance = - eTST.convertToAssets(aggrCurrenteTSTsecondaryShareBalance); - uint256 aggrNeweTSTUnderlyingBalance = aggrCurrenteTSTUnderlyingBalance * 11e17 / 1e18; - uint256 aggrNeweTSTsecondaryUnderlyingBalance = aggrCurrenteTSTsecondaryUnderlyingBalance * 11e17 / 1e18; - eTSTYield = aggrNeweTSTUnderlyingBalance - aggrCurrenteTSTUnderlyingBalance; - eTSTsecondaryYield = aggrNeweTSTsecondaryUnderlyingBalance - aggrCurrenteTSTsecondaryUnderlyingBalance; - - assetTST.mint(address(eTST), eTSTYield); - assetTST.mint(address(eTSTsecondary), eTSTsecondaryYield); - eTST.skim(type(uint256).max, address(eulerAggregationVault)); - eTSTsecondary.skim(type(uint256).max, address(eulerAggregationVault)); - } - // harvest vm.prank(user1); eulerAggregationVault.harvest(); - vm.warp(block.timestamp + 2 weeks); - // vm.prank(manager); - // eulerAggregationVault.removeStrategy(address(eTSTsecondary)); - - // vm.mockCall( - // address(eTST), - // abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationVault)), - // abi.encode(0) - // ); - // vm.mockCall( - // address(eTSTsecondary), - // abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationVault)), - // abi.encode(0) - // ); - { - uint256 amountToWithdraw = eulerAggregationVault.balanceOf(user1); + // mock decrease by 10% + uint256 aggrCurrentStrategyBalance = eTST.balanceOf(address(eulerAggregationVault)); + uint256 aggrCurrentStrategyBalanceAfterNegYield = aggrCurrentStrategyBalance * 9e17 / 1e18; + vm.mockCall( + address(eTST), + abi.encodeWithSelector(EVault.maxWithdraw.selector, address(eulerAggregationVault)), + abi.encode(aggrCurrentStrategyBalanceAfterNegYield) + ); - vm.prank(user1); - // vm.expectRevert(WithdrawalQueue.NotEnoughAssets.selector); - eulerAggregationVault.redeem(amountToWithdraw, user1, user1); - } - // vm.clearMockedCalls(); + uint256 amountToRedeem = eulerAggregationVault.balanceOf(user1); + vm.prank(user1); + vm.expectRevert(ErrorsLib.NotEnoughAssets.selector); + eulerAggregationVault.redeem(amountToRedeem, user1, user1); + + vm.clearMockedCalls(); } } From 1fe9f9d7f6af3a2f03c25661d02820829d2ef2ca Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 5 Aug 2024 18:06:17 +0400 Subject: [PATCH 313/316] chore: update EVC dependency --- lib/ethereum-vault-connector | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ethereum-vault-connector b/lib/ethereum-vault-connector index f791f94e..41ab56a6 160000 --- a/lib/ethereum-vault-connector +++ b/lib/ethereum-vault-connector @@ -1 +1 @@ -Subproject commit f791f94e6e790dd82041908983b57412dc04fb84 +Subproject commit 41ab56a6ed17a1e7c228467f4666f6b2a520392c From 9be83cd7f1e5e2639727ec60ad1fbff9fcc1b09a Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 5 Aug 2024 19:24:29 +0400 Subject: [PATCH 314/316] feat: EVC integration --- src/core/EulerAggregationVault.sol | 8 ++++++++ src/core/EulerAggregationVaultFactory.sol | 4 ++++ src/core/interface/IEulerAggregationVault.sol | 1 + src/core/module/Rebalance.sol | 3 +-- test/A16zPropertyTests.t.sol | 5 +++++ test/common/EulerAggregationVaultBase.t.sol | 1 + test/e2e/BalanceForwarderE2ETest.t.sol | 1 + test/echidna/CryticERC4626TestsHarness.t.sol | 6 ++++++ 8 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 00963065..88d04f4a 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -7,6 +7,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IBalanceTracker} from "reward-streams/interfaces/IBalanceTracker.sol"; import {IEulerAggregationVault} from "./interface/IEulerAggregationVault.sol"; // contracts +import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgradeable.sol"; import {Dispatch} from "./Dispatch.sol"; import { ERC20Upgradeable, @@ -16,6 +17,7 @@ import {ERC20VotesUpgradeable} from "@openzeppelin-upgradeable/token/ERC20/exten import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {Shared} from "./common/Shared.sol"; +import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; // libs import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; @@ -36,6 +38,7 @@ contract EulerAggregationVault is ERC20VotesUpgradeable, AccessControlEnumerableUpgradeable, Dispatch, + EVCUtil, IEulerAggregationVault { using SafeERC20 for IERC20; @@ -55,6 +58,7 @@ contract EulerAggregationVault is /// @dev Constructor. constructor(ConstructorParams memory _constructorParams) + EVCUtil(_constructorParams.evc) Dispatch( _constructorParams.rewardsModule, _constructorParams.hooksModule, @@ -533,4 +537,8 @@ contract EulerAggregationVault is balanceTracker.balanceTrackerHook(to, super.balanceOf(to), false); } } + + function _msgSender() internal view override (ContextUpgradeable, EVCUtil) returns (address) { + return EVCUtil._msgSender(); + } } diff --git a/src/core/EulerAggregationVaultFactory.sol b/src/core/EulerAggregationVaultFactory.sol index 836404c1..dee67725 100644 --- a/src/core/EulerAggregationVaultFactory.sol +++ b/src/core/EulerAggregationVaultFactory.sol @@ -13,6 +13,7 @@ contract EulerAggregationVaultFactory { error InvalidQuery(); /// core dependencies + address public immutable evc; address public immutable balanceTracker; /// core modules implementations addresses address public immutable rewardsModule; @@ -29,6 +30,7 @@ contract EulerAggregationVaultFactory { /// @dev Init params struct. struct FactoryParams { address owner; + address evc; address balanceTracker; address rewardsModuleImpl; address hooksModuleImpl; @@ -43,6 +45,7 @@ contract EulerAggregationVaultFactory { /// @notice Constructor. /// @param _factoryParams FactoryParams struct. constructor(FactoryParams memory _factoryParams) { + evc = _factoryParams.evc; balanceTracker = _factoryParams.balanceTracker; rewardsModule = Clones.clone(_factoryParams.rewardsModuleImpl); hooksModule = Clones.clone(_factoryParams.hooksModuleImpl); @@ -53,6 +56,7 @@ contract EulerAggregationVaultFactory { IEulerAggregationVault.ConstructorParams memory aggregationVaultConstructorParams = IEulerAggregationVault .ConstructorParams({ + evc: evc, rewardsModule: rewardsModule, hooksModule: hooksModule, feeModule: feeModule, diff --git a/src/core/interface/IEulerAggregationVault.sol b/src/core/interface/IEulerAggregationVault.sol index 724228c4..d1a9bb16 100644 --- a/src/core/interface/IEulerAggregationVault.sol +++ b/src/core/interface/IEulerAggregationVault.sol @@ -6,6 +6,7 @@ import {AmountCap} from "../lib/AmountCapLib.sol"; interface IEulerAggregationVault { /// @dev Struct to pass to constrcutor. struct ConstructorParams { + address evc; address rewardsModule; address hooksModule; address feeModule; diff --git a/src/core/module/Rebalance.sol b/src/core/module/Rebalance.sol index 2278e693..db3fb2ef 100644 --- a/src/core/module/Rebalance.sol +++ b/src/core/module/Rebalance.sol @@ -7,7 +7,6 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; // contracts import {Shared} from "../common/Shared.sol"; -import {ContextUpgradeable} from "@openzeppelin-upgradeable/utils/ContextUpgradeable.sol"; // libs import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; @@ -16,7 +15,7 @@ import {AmountCapLib, AmountCap} from "../lib/AmountCapLib.sol"; import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol"; import {EventsLib as Events} from "../lib/EventsLib.sol"; -abstract contract RebalanceModule is ContextUpgradeable, Shared { +abstract contract RebalanceModule is Shared { using SafeERC20 for IERC20; using SafeCast for uint256; using AmountCapLib for AmountCap; diff --git a/test/A16zPropertyTests.t.sol b/test/A16zPropertyTests.t.sol index 05954319..2ae9c656 100644 --- a/test/A16zPropertyTests.t.sol +++ b/test/A16zPropertyTests.t.sol @@ -14,10 +14,13 @@ import {EulerAggregationVaultFactory} from "../src/core/EulerAggregationVaultFac import {Strategy} from "../src/core/module/Strategy.sol"; // mocks import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol"; +// evc setup +import {EthereumVaultConnector} from "ethereum-vault-connector/EthereumVaultConnector.sol"; contract A16zPropertyTests is ERC4626Test { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; + EthereumVaultConnector public evc; address public factoryOwner; // core modules @@ -33,6 +36,7 @@ contract A16zPropertyTests is ERC4626Test { function setUp() public override { factoryOwner = makeAddr("FACTORY_OWNER"); + evc = new EthereumVaultConnector(); rewardsImpl = new Rewards(); hooksImpl = new Hooks(); @@ -43,6 +47,7 @@ contract A16zPropertyTests is ERC4626Test { EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ owner: factoryOwner, + evc: address(evc), balanceTracker: address(0), rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), diff --git a/test/common/EulerAggregationVaultBase.t.sol b/test/common/EulerAggregationVaultBase.t.sol index 1aa0376d..7988c557 100644 --- a/test/common/EulerAggregationVaultBase.t.sol +++ b/test/common/EulerAggregationVaultBase.t.sol @@ -57,6 +57,7 @@ contract EulerAggregationVaultBase is EVaultTestBase { EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ owner: deployer, + evc: address(evc), balanceTracker: address(0), rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), diff --git a/test/e2e/BalanceForwarderE2ETest.t.sol b/test/e2e/BalanceForwarderE2ETest.t.sol index f1ad8b56..b47673fe 100644 --- a/test/e2e/BalanceForwarderE2ETest.t.sol +++ b/test/e2e/BalanceForwarderE2ETest.t.sol @@ -27,6 +27,7 @@ contract BalanceForwarderE2ETest is EulerAggregationVaultBase { EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ owner: deployer, + evc: address(evc), balanceTracker: trackingReward, rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), diff --git a/test/echidna/CryticERC4626TestsHarness.t.sol b/test/echidna/CryticERC4626TestsHarness.t.sol index 4bb9c69b..5b0bbade 100644 --- a/test/echidna/CryticERC4626TestsHarness.t.sol +++ b/test/echidna/CryticERC4626TestsHarness.t.sol @@ -13,10 +13,13 @@ import {WithdrawalQueue} from "../../src/core/module/WithdrawalQueue.sol"; import {EulerAggregationVaultFactory} from "../../src/core/EulerAggregationVaultFactory.sol"; import {Strategy} from "../../src/core/module/Strategy.sol"; import {TestERC20Token} from "crytic-properties/ERC4626/util/TestERC20Token.sol"; +// evc setup +import {EthereumVaultConnector} from "ethereum-vault-connector/EthereumVaultConnector.sol"; contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { uint256 public constant CASH_RESERVE_ALLOCATION_POINTS = 1000e18; + EthereumVaultConnector public evc; address factoryDeployer; // core modules @@ -31,6 +34,8 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { EulerAggregationVault eulerAggregationVault; constructor() { + evc = new EthereumVaultConnector(); + rewardsImpl = new Rewards(); hooksImpl = new Hooks(); feeModuleImpl = new Fee(); @@ -40,6 +45,7 @@ contract CryticERC4626TestsHarness is CryticERC4626PropertyTests { EulerAggregationVaultFactory.FactoryParams memory factoryParams = EulerAggregationVaultFactory.FactoryParams({ owner: address(this), + evc: address(evc), balanceTracker: address(0), rewardsModuleImpl: address(rewardsImpl), hooksModuleImpl: address(hooksImpl), From 542d1d3b0febfd3b418f6ffa6769f25cedce444d Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:36:13 +0400 Subject: [PATCH 315/316] Add onlyEVCAccountOwner() modifier --- foundry.toml | 2 +- src/core/EVCUtilOp.sol | 166 +++++++++++++++++++++++++++++ src/core/EulerAggregationVault.sol | 38 ++++++- 3 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 src/core/EVCUtilOp.sol diff --git a/foundry.toml b/foundry.toml index 8602ff6b..d9474a35 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,7 +4,7 @@ out = "out" libs = ["lib"] test = 'test' optimizer = true -optimizer_runs = 300 +optimizer_runs = 100 solc = "0.8.24" gas_reports = ["*"] fs_permissions = [{ access = "read", path = "./"}] diff --git a/src/core/EVCUtilOp.sol b/src/core/EVCUtilOp.sol new file mode 100644 index 00000000..c3d2ebf7 --- /dev/null +++ b/src/core/EVCUtilOp.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; +import {ExecutionContext, EC} from "ethereum-vault-connector/ExecutionContext.sol"; + +/// @title EVCUtil +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice This contract is an abstract base contract for interacting with the Ethereum Vault Connector (EVC). +/// It provides utility functions for authenticating the callers in the context of the EVC, a pattern for enforcing the +/// contracts to be called through the EVC. +abstract contract EVCUtil { + using ExecutionContext for EC; + + uint160 internal constant ACCOUNT_ID_OFFSET = 8; + IEVC internal immutable evc; + + error EVC_InvalidAddress(); + error NotAuthorized(); + error ControllerDisabled(); + + constructor(address _evc) { + if (_evc == address(0)) revert EVC_InvalidAddress(); + + evc = IEVC(_evc); + } + + /// @notice Returns the address of the Ethereum Vault Connector (EVC) used by this contract. + /// @return The address of the EVC contract. + function EVC() external view returns (address) { + return address(evc); + } + + /// @notice Ensures that the msg.sender is the EVC by using the EVC callback functionality if necessary. + /// @dev Optional to use for functions requiring account and vault status checks to enforce predictable behavior. + /// @dev If this modifier used in conjuction with any other modifier, it must appear as the first (outermost) + /// modifier of the function. + modifier callThroughEVC() virtual { + if (msg.sender == address(evc)) { + _; + } else { + address _evc = address(evc); + + assembly { + mstore(0, 0x1f8b521500000000000000000000000000000000000000000000000000000000) // EVC.call selector + mstore(4, address()) // EVC.call 1st argument - address(this) + mstore(36, caller()) // EVC.call 2nd argument - msg.sender + mstore(68, callvalue()) // EVC.call 3rd argument - msg.value + mstore(100, 128) // EVC.call 4th argument - msg.data, offset to the start of encoding - 128 bytes + mstore(132, calldatasize()) // msg.data length + calldatacopy(164, 0, calldatasize()) // original calldata + + // abi encoded bytes array should be zero padded so its length is a multiple of 32 + // store zero word after msg.data bytes and round up calldatasize to nearest multiple of 32 + mstore(add(164, calldatasize()), 0) + let result := call(gas(), _evc, callvalue(), 0, add(164, and(add(calldatasize(), 31), not(31))), 0, 0) + + returndatacopy(0, 0, returndatasize()) + switch result + case 0 { revert(0, returndatasize()) } + default { return(64, sub(returndatasize(), 64)) } // strip bytes encoding from call return + } + } + } + + /// @notice Ensures that the caller is the EVC in the appropriate context. + /// @dev Should be used for checkAccountStatus and checkVaultStatus functions. + modifier onlyEVCWithChecksInProgress() virtual { + if (msg.sender != address(evc) || !evc.areChecksInProgress()) { + revert NotAuthorized(); + } + + _; + } + + /// @notice Ensures a standard authentication path on the EVC. + /// @dev This modifier checks if the caller is the EVC and if so, verifies the execution context. + /// It reverts if the operator is authenticated, control collateral is in progress, or checks are in progress. + /// It reverts if the authenticated account owner is known and it is not the account owner. + /// @dev It assumes that if the caller is not the EVC, the caller is the account owner. + /// @dev This modifier must not be used on functions utilized by liquidation flows, i.e. transfer or withdraw. + /// @dev This modifier must not be used on checkAccountStatus and checkVaultStatus functions. + /// @dev This modifier can be used on access controlled functions to prevent non-standard authentication paths on + /// the EVC. + modifier onlyEVCAccountOwner() virtual { + _onlyEVCAccountOwner(); + _; + } + + function _onlyEVCAccountOwner() internal view { + if (msg.sender == address(evc)) { + EC ec = EC.wrap(evc.getRawExecutionContext()); + + if (ec.isOperatorAuthenticated() || ec.isControlCollateralInProgress() || ec.areChecksInProgress()) { + revert NotAuthorized(); + } + + address onBehalfOfAccount = ec.getOnBehalfOfAccount(); + address owner = evc.getAccountOwner(onBehalfOfAccount); + + if (owner != address(0) && owner != onBehalfOfAccount) { + revert NotAuthorized(); + } + } + } + + /// @notice Checks whether the specified account and the other account have the same owner. + /// @dev The function is used to check whether one account is authorized to perform operations on behalf of the + /// other. Accounts are considered to have a common owner if they share the first 19 bytes of their address. + /// @param account The address of the account that is being checked. + /// @param otherAccount The address of the other account that is being checked. + /// @return A boolean flag that indicates whether the accounts have the same owner. + function _haveCommonOwner(address account, address otherAccount) internal pure returns (bool) { + bool result; + assembly { + result := lt(xor(account, otherAccount), 0x100) + } + return result; + } + + /// @notice Returns the address prefix of the specified account. + /// @dev The address prefix is the first 19 bytes of the account address. + /// @param account The address of the account whose address prefix is being retrieved. + /// @return A bytes19 value that represents the address prefix of the account. + function _getAddressPrefix(address account) internal pure returns (bytes19) { + return bytes19(uint152(uint160(account) >> ACCOUNT_ID_OFFSET)); + } + + /// @notice Retrieves the message sender in the context of the EVC. + /// @dev This function returns the account on behalf of which the current operation is being performed, which is + /// either msg.sender or the account authenticated by the EVC. + /// @return The address of the message sender. + function _msgSender() internal view virtual returns (address) { + address sender = msg.sender; + + if (sender == address(evc)) { + (sender,) = evc.getCurrentOnBehalfOfAccount(address(0)); + } + + return sender; + } + + /// @notice Retrieves the message sender in the context of the EVC for a borrow operation. + /// @dev This function returns the account on behalf of which the current operation is being performed, which is + /// either msg.sender or the account authenticated by the EVC. This function reverts if this contract is not enabled + /// as a controller for the account on behalf of which the operation is being executed. + /// @return The address of the message sender. + function _msgSenderForBorrow() internal view virtual returns (address) { + address sender = msg.sender; + bool controllerEnabled; + + if (sender == address(evc)) { + (sender, controllerEnabled) = evc.getCurrentOnBehalfOfAccount(address(this)); + } else { + controllerEnabled = evc.isControllerEnabled(sender, address(this)); + } + + if (!controllerEnabled) { + revert ControllerDisabled(); + } + + return sender; + } +} diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index 88d04f4a..bc7765c8 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -17,7 +17,8 @@ import {ERC20VotesUpgradeable} from "@openzeppelin-upgradeable/token/ERC20/exten import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {Shared} from "./common/Shared.sol"; -import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; +// import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; +import {EVCUtil} from "./EVCUtilOp.sol"; // libs import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; @@ -104,17 +105,25 @@ contract EulerAggregationVault is external override onlyRole(AGGREGATION_VAULT_MANAGER) + onlyEVCAccountOwner use(feeModule) {} /// @dev See {FeeModule-setPerformanceFee}. - function setPerformanceFee(uint96 _newFee) external override onlyRole(AGGREGATION_VAULT_MANAGER) use(feeModule) {} + function setPerformanceFee(uint96 _newFee) + external + override + onlyRole(AGGREGATION_VAULT_MANAGER) + onlyEVCAccountOwner + use(feeModule) + {} /// @dev See {RewardsModule-optInStrategyRewards}. function optInStrategyRewards(address _strategy) external override onlyRole(AGGREGATION_VAULT_MANAGER) + onlyEVCAccountOwner use(rewardsModule) {} @@ -123,6 +132,7 @@ contract EulerAggregationVault is external override onlyRole(AGGREGATION_VAULT_MANAGER) + onlyEVCAccountOwner use(rewardsModule) {} @@ -131,6 +141,7 @@ contract EulerAggregationVault is external override onlyRole(AGGREGATION_VAULT_MANAGER) + onlyEVCAccountOwner use(rewardsModule) {} @@ -139,6 +150,7 @@ contract EulerAggregationVault is external override onlyRole(AGGREGATION_VAULT_MANAGER) + onlyEVCAccountOwner use(rewardsModule) {} @@ -147,6 +159,7 @@ contract EulerAggregationVault is external override onlyRole(AGGREGATION_VAULT_MANAGER) + onlyEVCAccountOwner use(rewardsModule) {} @@ -155,6 +168,7 @@ contract EulerAggregationVault is external override onlyRole(AGGREGATION_VAULT_MANAGER) + onlyEVCAccountOwner use(hooksModule) {} @@ -163,20 +177,34 @@ contract EulerAggregationVault is external override onlyRole(STRATEGY_OPERATOR) + onlyEVCAccountOwner use(strategyModule) {} /// @dev See {StrategyModule-removeStrategy}. - function removeStrategy(address _strategy) external override onlyRole(STRATEGY_OPERATOR) use(strategyModule) {} + function removeStrategy(address _strategy) + external + override + onlyRole(STRATEGY_OPERATOR) + onlyEVCAccountOwner + use(strategyModule) + {} /// @dev See {StrategyModule-setStrategyCap}. - function setStrategyCap(address _strategy, uint16 _cap) external override onlyRole(GUARDIAN) use(strategyModule) {} + function setStrategyCap(address _strategy, uint16 _cap) + external + override + onlyRole(GUARDIAN) + onlyEVCAccountOwner + use(strategyModule) + {} /// @dev See {StrategyModule-adjustAllocationPoints}. function adjustAllocationPoints(address _strategy, uint256 _newPoints) external override onlyRole(GUARDIAN) + onlyEVCAccountOwner use(strategyModule) {} @@ -185,6 +213,7 @@ contract EulerAggregationVault is external override onlyRole(GUARDIAN) + onlyEVCAccountOwner use(strategyModule) {} @@ -202,6 +231,7 @@ contract EulerAggregationVault is external override onlyRole(WITHDRAWAL_QUEUE_MANAGER) + onlyEVCAccountOwner use(withdrawalQueueModule) {} From 4fcc7feb6686933a169ae6fe7428708e676310ba Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:05:52 +0400 Subject: [PATCH 316/316] chore: update dependency --- foundry.toml | 2 +- lib/ethereum-vault-connector | 2 +- src/core/EVCUtilOp.sol | 166 ----------------------------- src/core/EulerAggregationVault.sol | 3 +- 4 files changed, 3 insertions(+), 170 deletions(-) delete mode 100644 src/core/EVCUtilOp.sol diff --git a/foundry.toml b/foundry.toml index d9474a35..bad1ca9a 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,7 +4,7 @@ out = "out" libs = ["lib"] test = 'test' optimizer = true -optimizer_runs = 100 +optimizer_runs = 200 solc = "0.8.24" gas_reports = ["*"] fs_permissions = [{ access = "read", path = "./"}] diff --git a/lib/ethereum-vault-connector b/lib/ethereum-vault-connector index 41ab56a6..f39c76e5 160000 --- a/lib/ethereum-vault-connector +++ b/lib/ethereum-vault-connector @@ -1 +1 @@ -Subproject commit 41ab56a6ed17a1e7c228467f4666f6b2a520392c +Subproject commit f39c76e5dcdb53ab3a6a9864d9271f2054674ec5 diff --git a/src/core/EVCUtilOp.sol b/src/core/EVCUtilOp.sol deleted file mode 100644 index c3d2ebf7..00000000 --- a/src/core/EVCUtilOp.sol +++ /dev/null @@ -1,166 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {IEVC} from "ethereum-vault-connector/interfaces/IEthereumVaultConnector.sol"; -import {ExecutionContext, EC} from "ethereum-vault-connector/ExecutionContext.sol"; - -/// @title EVCUtil -/// @custom:security-contact security@euler.xyz -/// @author Euler Labs (https://www.eulerlabs.com/) -/// @notice This contract is an abstract base contract for interacting with the Ethereum Vault Connector (EVC). -/// It provides utility functions for authenticating the callers in the context of the EVC, a pattern for enforcing the -/// contracts to be called through the EVC. -abstract contract EVCUtil { - using ExecutionContext for EC; - - uint160 internal constant ACCOUNT_ID_OFFSET = 8; - IEVC internal immutable evc; - - error EVC_InvalidAddress(); - error NotAuthorized(); - error ControllerDisabled(); - - constructor(address _evc) { - if (_evc == address(0)) revert EVC_InvalidAddress(); - - evc = IEVC(_evc); - } - - /// @notice Returns the address of the Ethereum Vault Connector (EVC) used by this contract. - /// @return The address of the EVC contract. - function EVC() external view returns (address) { - return address(evc); - } - - /// @notice Ensures that the msg.sender is the EVC by using the EVC callback functionality if necessary. - /// @dev Optional to use for functions requiring account and vault status checks to enforce predictable behavior. - /// @dev If this modifier used in conjuction with any other modifier, it must appear as the first (outermost) - /// modifier of the function. - modifier callThroughEVC() virtual { - if (msg.sender == address(evc)) { - _; - } else { - address _evc = address(evc); - - assembly { - mstore(0, 0x1f8b521500000000000000000000000000000000000000000000000000000000) // EVC.call selector - mstore(4, address()) // EVC.call 1st argument - address(this) - mstore(36, caller()) // EVC.call 2nd argument - msg.sender - mstore(68, callvalue()) // EVC.call 3rd argument - msg.value - mstore(100, 128) // EVC.call 4th argument - msg.data, offset to the start of encoding - 128 bytes - mstore(132, calldatasize()) // msg.data length - calldatacopy(164, 0, calldatasize()) // original calldata - - // abi encoded bytes array should be zero padded so its length is a multiple of 32 - // store zero word after msg.data bytes and round up calldatasize to nearest multiple of 32 - mstore(add(164, calldatasize()), 0) - let result := call(gas(), _evc, callvalue(), 0, add(164, and(add(calldatasize(), 31), not(31))), 0, 0) - - returndatacopy(0, 0, returndatasize()) - switch result - case 0 { revert(0, returndatasize()) } - default { return(64, sub(returndatasize(), 64)) } // strip bytes encoding from call return - } - } - } - - /// @notice Ensures that the caller is the EVC in the appropriate context. - /// @dev Should be used for checkAccountStatus and checkVaultStatus functions. - modifier onlyEVCWithChecksInProgress() virtual { - if (msg.sender != address(evc) || !evc.areChecksInProgress()) { - revert NotAuthorized(); - } - - _; - } - - /// @notice Ensures a standard authentication path on the EVC. - /// @dev This modifier checks if the caller is the EVC and if so, verifies the execution context. - /// It reverts if the operator is authenticated, control collateral is in progress, or checks are in progress. - /// It reverts if the authenticated account owner is known and it is not the account owner. - /// @dev It assumes that if the caller is not the EVC, the caller is the account owner. - /// @dev This modifier must not be used on functions utilized by liquidation flows, i.e. transfer or withdraw. - /// @dev This modifier must not be used on checkAccountStatus and checkVaultStatus functions. - /// @dev This modifier can be used on access controlled functions to prevent non-standard authentication paths on - /// the EVC. - modifier onlyEVCAccountOwner() virtual { - _onlyEVCAccountOwner(); - _; - } - - function _onlyEVCAccountOwner() internal view { - if (msg.sender == address(evc)) { - EC ec = EC.wrap(evc.getRawExecutionContext()); - - if (ec.isOperatorAuthenticated() || ec.isControlCollateralInProgress() || ec.areChecksInProgress()) { - revert NotAuthorized(); - } - - address onBehalfOfAccount = ec.getOnBehalfOfAccount(); - address owner = evc.getAccountOwner(onBehalfOfAccount); - - if (owner != address(0) && owner != onBehalfOfAccount) { - revert NotAuthorized(); - } - } - } - - /// @notice Checks whether the specified account and the other account have the same owner. - /// @dev The function is used to check whether one account is authorized to perform operations on behalf of the - /// other. Accounts are considered to have a common owner if they share the first 19 bytes of their address. - /// @param account The address of the account that is being checked. - /// @param otherAccount The address of the other account that is being checked. - /// @return A boolean flag that indicates whether the accounts have the same owner. - function _haveCommonOwner(address account, address otherAccount) internal pure returns (bool) { - bool result; - assembly { - result := lt(xor(account, otherAccount), 0x100) - } - return result; - } - - /// @notice Returns the address prefix of the specified account. - /// @dev The address prefix is the first 19 bytes of the account address. - /// @param account The address of the account whose address prefix is being retrieved. - /// @return A bytes19 value that represents the address prefix of the account. - function _getAddressPrefix(address account) internal pure returns (bytes19) { - return bytes19(uint152(uint160(account) >> ACCOUNT_ID_OFFSET)); - } - - /// @notice Retrieves the message sender in the context of the EVC. - /// @dev This function returns the account on behalf of which the current operation is being performed, which is - /// either msg.sender or the account authenticated by the EVC. - /// @return The address of the message sender. - function _msgSender() internal view virtual returns (address) { - address sender = msg.sender; - - if (sender == address(evc)) { - (sender,) = evc.getCurrentOnBehalfOfAccount(address(0)); - } - - return sender; - } - - /// @notice Retrieves the message sender in the context of the EVC for a borrow operation. - /// @dev This function returns the account on behalf of which the current operation is being performed, which is - /// either msg.sender or the account authenticated by the EVC. This function reverts if this contract is not enabled - /// as a controller for the account on behalf of which the operation is being executed. - /// @return The address of the message sender. - function _msgSenderForBorrow() internal view virtual returns (address) { - address sender = msg.sender; - bool controllerEnabled; - - if (sender == address(evc)) { - (sender, controllerEnabled) = evc.getCurrentOnBehalfOfAccount(address(this)); - } else { - controllerEnabled = evc.isControllerEnabled(sender, address(this)); - } - - if (!controllerEnabled) { - revert ControllerDisabled(); - } - - return sender; - } -} diff --git a/src/core/EulerAggregationVault.sol b/src/core/EulerAggregationVault.sol index bc7765c8..52d1f653 100644 --- a/src/core/EulerAggregationVault.sol +++ b/src/core/EulerAggregationVault.sol @@ -17,8 +17,7 @@ import {ERC20VotesUpgradeable} from "@openzeppelin-upgradeable/token/ERC20/exten import {AccessControlEnumerableUpgradeable} from "@openzeppelin-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; import {Shared} from "./common/Shared.sol"; -// import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; -import {EVCUtil} from "./EVCUtilOp.sol"; +import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; // libs import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";