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 01/25] 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 02/25] 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 03/25] 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 04/25] 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 05/25] 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 06/25] 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 07/25] 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 08/25] 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 09/25] 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 10/25] 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 11/25] 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 12/25] 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 13/25] 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 14/25] 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 15/25] 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 16/25] 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 17/25] 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 18/25] 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 19/25] 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 20/25] 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 21/25] 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 22/25] 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 23/25] 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 24/25] 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 25/25] 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