Skip to content

Commit

Permalink
Merge pull request #34 from euler-xyz/invariants-test
Browse files Browse the repository at this point in the history
Test: invariants tests
  • Loading branch information
haythemsellami authored Jul 4, 2024
2 parents 76bef1d + 788a096 commit 22552d5
Show file tree
Hide file tree
Showing 30 changed files with 861 additions and 66 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/echidna.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 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
11 changes: 7 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,19 @@ jobs:
version: nightly

- name: Run foundry build
run: forge build --force --sizes
run: forge build --force --sizes --skip test

- name: Run foundry fmt check
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
run: FOUNDRY_PROFILE=coverage forge coverage --report summary

- name: Run foundry invariants
run: forge clean && FOUNDRY_PROFILE=invariant forge test
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ lcov.info
coverage

# gas
.gas-snapshot
.gas-snapshot

# echidna
crytic-export/
/test/echidna/_corpus/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
26 changes: 3 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,28 +39,8 @@ $ forge fmt
$ forge snapshot
```

### Anvil
### Run Echidna

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
$ echidna test/echidna/CryticERC4626TestsHarness.t.sol --contract CryticERC4626TestsHarness --config test/echidna/config/echidna.config.yaml
```
13 changes: 8 additions & 5 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "./"}]

Expand All @@ -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]
Expand All @@ -50,16 +50,19 @@ match_contract = "Fuzz"

[profile.invariant]
runs = 256
depth = 15
depth = 500
fail_on_revert = false
call_override = false
dictionary_weight = 80
include_storage = true
include_push_bytes = true
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
Expand Down
1 change: 1 addition & 0 deletions lib/properties
Submodule properties added at bb1b78
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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/
@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
crytic-properties/=lib/properties/contracts/
4 changes: 1 addition & 3 deletions src/core/Dispatch.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions src/core/EulerAggregationLayer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -488,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();
Expand Down
2 changes: 1 addition & 1 deletion src/core/common/Shared.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {ErrorsLib as Errors} from "../lib/ErrorsLib.sol";
/// @title Shared contract
/// @custom:security-contact [email protected]
/// @author Euler Labs (https://www.eulerlabs.com/)
contract Shared {
abstract contract Shared {
using HooksLib for uint32;

uint8 internal constant REENTRANCYLOCK__UNLOCKED = 1;
Expand Down
3 changes: 3 additions & 0 deletions src/core/interface/IEulerAggregationLayer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/core/lib/ErrorsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ library ErrorsLib {
error NotWithdrawaQueue();
error InvalidPlugin();
error NotRebalancer();
error InvalidAllocationPoints();
error CanNotRemoveStartegyWithAllocatedAmount();
}
5 changes: 5 additions & 0 deletions src/core/module/AllocationPoints.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -98,12 +101,14 @@ 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;
strategyStorage.active = false;
strategyStorage.allocationPoints = 0;
strategyStorage.cap = 0;

// remove from withdrawalQueue
IWithdrawalQueue($.withdrawalQueue).removeStrategyFromWithdrawalQueue(_strategy);
Expand Down
8 changes: 3 additions & 5 deletions src/core/module/Fee.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -16,12 +14,12 @@ import {EventsLib as Events} from "../lib/EventsLib.sol";
/// @title FeeModule contract
/// @custom:security-contact [email protected]
/// @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;

/// @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;
Expand All @@ -34,7 +32,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();

Expand Down
31 changes: 16 additions & 15 deletions src/plugin/Rebalancer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,32 @@ 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]);
}
}

/// @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 _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;

Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -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);
}
}
1 change: 1 addition & 0 deletions src/plugin/WithdrawalQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ contract WithdrawalQueue is AccessControlEnumerableUpgradeable, IWithdrawalQueue
}
}

// is this possible?
if (_availableAssets < _assets) {
revert NotEnoughAssets();
}
Expand Down
Loading

0 comments on commit 22552d5

Please sign in to comment.