├── .github ├── CODEOWNERS └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── SECURITY.md ├── audits ├── Cantina_competition_report.pdf ├── Certora_EVK_report.pdf ├── ChainSecurity_EVK_report.pdf ├── Electisec_setLTV_report.pdf ├── EnigmaDark_EVK_report.pdf ├── M4rio.eth_setLTV_report.pdf ├── Omniscia_EVK_report.pdf ├── OpenZeppelin_EVK_report.pdf ├── OpenZeppelin_Synths_report.pdf ├── Spearbit_EVK_report.pdf ├── yAudit_EVK_report.pdf └── yAudit_competition_fixes_report.pdf ├── certora ├── conf │ ├── Cache.conf │ ├── ERC4626Split │ │ ├── README.md │ │ ├── VaultERC4626-assetsMoreThanSupply.conf │ │ ├── VaultERC4626-convertToAssetsWeakAdditivity.conf │ │ ├── VaultERC4626-convertToSharesWeakAdditivity.conf │ │ ├── VaultERC4626-depositMonotonicity.conf │ │ ├── VaultERC4626-dustFavorsTheHouse.conf │ │ ├── VaultERC4626-dustFavorsTheHouseAssets.conf │ │ ├── VaultERC4626-noAssetsIfNoSupply.conf │ │ ├── VaultERC4626-noSupplyIfNoAssets.conf │ │ ├── VaultERC4626-onlyContributionMethodsReduce.conf │ │ ├── VaultERC4626-totalsMonotonicity.conf │ │ ├── VaultERC4626-vaultSolvency-most.conf │ │ ├── VaultERC4626-vaultSolvency-redeem.conf │ │ ├── VaultERC4626-vaultSolvency-withdraw-as-rule.conf │ │ ├── VaultERC4626-vaultSolvency-withdraw.conf │ │ └── VaultERC4626.conf │ ├── EVault │ │ └── modules │ │ │ ├── BalanceForwarder.conf │ │ │ ├── Governance.conf │ │ │ ├── Liquidation.conf │ │ │ ├── RiskManager.conf │ │ │ └── Vault.conf │ └── healthStatus │ │ ├── BalanceForwarderHealthStatus.conf │ │ ├── BorrowingHealthStatus.conf │ │ ├── ETokenCollateralHealthStatus.conf │ │ ├── GovernanceHealthStatus.conf │ │ ├── InitializeHealthStatus.conf │ │ ├── LiquidateHealthStatus.conf │ │ ├── LiquidationHealthStatus.conf │ │ ├── README.md │ │ ├── TokenHealthStatus.conf │ │ ├── UnderlyingTokenHealthStatus.conf │ │ └── VaultHealthStatus.conf ├── harness │ ├── AbstractBaseHarness.sol │ ├── BaseHarness.sol │ ├── CacheHarness.sol │ ├── ERC4626Harness.sol │ ├── EVCHarness.sol │ ├── EVaultHarness.sol │ ├── ModuleDispatchHarness.sol │ ├── TokenHarness.sol │ ├── healthStatus │ │ ├── BalanceForwarderHSHarness.sol │ │ ├── BorrowingHSHarness.sol │ │ ├── GovernanceHSHarness.sol │ │ ├── InitializeHSHarness.sol │ │ ├── LiquidationHSHarness.sol │ │ ├── TokenHSHarness.sol │ │ └── VaultHSHarness.sol │ └── modules │ │ ├── BalanceForwarderHarness.sol │ │ ├── BorrowingHarness.sol │ │ ├── GovernanceHarness.sol │ │ ├── InitializeHarness.sol │ │ ├── LiquidationHarness.sol │ │ ├── RiskManagerHarness.sol │ │ ├── TokenHarness.sol │ │ └── VaultHarness.sol ├── helpers │ ├── DummyERC20A.sol │ ├── DummyERC20B.sol │ ├── DummyERC20Impl.sol │ ├── DummyETokenA.sol │ └── DummyETokenB.sol ├── scripts │ ├── runERC4626RulesSeparatelyBaseConf.py │ ├── runERC4626RulesSplitConfs.py │ └── runHealthStatusAllModules.py └── specs │ ├── BalanceForwarder.spec │ ├── Base.spec │ ├── Cache.spec │ ├── GhostPow.spec │ ├── Governance.spec │ ├── HealthStatusInvariant.spec │ ├── LiquidateHealthStatus.spec │ ├── Liquidation.spec │ ├── LoadVaultSummary.spec │ ├── RiskManager.spec │ ├── Vault.spec │ └── VaultERC4626.spec ├── docs ├── autoramping.svg ├── evault.svg ├── nesting.svg ├── specs.md ├── update-setLTV.md └── whitepaper.md ├── foundry.toml ├── medusa.json ├── remappings.txt ├── slither.config.json ├── src ├── EVault │ ├── DToken.sol │ ├── Dispatch.sol │ ├── EVault.sol │ ├── IEVault.sol │ ├── modules │ │ ├── BalanceForwarder.sol │ │ ├── Borrowing.sol │ │ ├── Governance.sol │ │ ├── Initialize.sol │ │ ├── Liquidation.sol │ │ ├── RiskManager.sol │ │ ├── Token.sol │ │ └── Vault.sol │ └── shared │ │ ├── AssetTransfers.sol │ │ ├── BalanceUtils.sol │ │ ├── Base.sol │ │ ├── BorrowUtils.sol │ │ ├── Cache.sol │ │ ├── Constants.sol │ │ ├── EVCClient.sol │ │ ├── Errors.sol │ │ ├── Events.sol │ │ ├── LTVUtils.sol │ │ ├── LiquidityUtils.sol │ │ ├── Storage.sol │ │ ├── lib │ │ ├── AddressUtils.sol │ │ ├── ConversionHelpers.sol │ │ ├── ProxyUtils.sol │ │ ├── RPow.sol │ │ ├── RevertBytes.sol │ │ └── SafeERC20Lib.sol │ │ └── types │ │ ├── AmountCap.sol │ │ ├── Assets.sol │ │ ├── ConfigAmount.sol │ │ ├── Flags.sol │ │ ├── LTVConfig.sol │ │ ├── Owed.sol │ │ ├── Shares.sol │ │ ├── Snapshot.sol │ │ ├── Types.sol │ │ ├── UserStorage.sol │ │ ├── VaultCache.sol │ │ └── VaultStorage.sol ├── GenericFactory │ ├── BeaconProxy.sol │ ├── GenericFactory.sol │ └── MetaProxyDeployer.sol ├── InterestRateModels │ ├── IIRM.sol │ └── IRMLinearKink.sol ├── ProtocolConfig │ ├── IProtocolConfig.sol │ └── ProtocolConfig.sol ├── SequenceRegistry │ └── SequenceRegistry.sol ├── Synths │ ├── ERC20EVCCompatible.sol │ ├── ESynth.sol │ ├── EulerSavingsRate.sol │ ├── HookTargetSynth.sol │ ├── IRMSynth.sol │ └── PegStabilityModule.sol └── interfaces │ ├── IBalanceTracker.sol │ ├── IFlashLoan.sol │ ├── IHookTarget.sol │ ├── IPermit2.sol │ ├── IPriceOracle.sol │ └── ISequenceRegistry.sol └── test ├── helpers ├── AssertionsCustomTypes.sol └── MathTesting.sol ├── invariant └── SimpleCriticalChecks.t.sol ├── invariants ├── CryticToFoundry.t.sol ├── HandlerAggregator.t.sol ├── Invariants.t.sol ├── InvariantsSpec.t.sol ├── Setup.t.sol ├── Tester.t.sol ├── _config │ └── echidna_config.yaml ├── base │ ├── BaseHandler.t.sol │ ├── BaseHooks.t.sol │ ├── BaseStorage.t.sol │ ├── BaseTest.t.sol │ └── ProtocolAssertions.t.sol ├── handlers │ ├── external │ │ └── EVCHandler.t.sol │ ├── interfaces │ │ └── ILiquidationModuleHandler.sol │ ├── modules │ │ ├── BalanceForwarderModuleHandler.t.sol │ │ ├── BorrowingModuleHandler.t.sol │ │ ├── GovernanceModuleHandler.t.sol │ │ ├── LiquidationModuleHandler.t.sol │ │ ├── RiskManagerModuleHandler.t.sol │ │ ├── TokenModuleHandler.t.sol │ │ └── VaultModuleHandler.t.sol │ └── simulators │ │ ├── DonationAttackHandler.t.sol │ │ ├── FlashLoanHandler.t.sol │ │ ├── IRMHandler.t.sol │ │ └── PriceOracleHandler.t.sol ├── helpers │ ├── VaultBaseGetters.sol │ └── extended │ │ ├── EVaultExtended.sol │ │ ├── FunctionOverrides.sol │ │ └── ModulesExtended.sol ├── hooks │ ├── BorrowingBeforeAfterHooks.t.sol │ ├── HookAggregator.t.sol │ └── VaultBeforeAfterHooks.t.sol ├── invariants │ ├── BaseInvariants.t.sol │ ├── BorrowingModuleInvariants.t.sol │ ├── InterestInvariants.t.sol │ ├── LiquidationModuleInvariants.t.sol │ ├── TokenModuleInvariants.t.sol │ └── VaultModuleInvariants.t.sol └── utils │ ├── Actor.sol │ ├── DeployPermit2.sol │ ├── Pretty.sol │ ├── PropertiesAsserts.sol │ ├── PropertiesConstants.sol │ └── StdAsserts.sol ├── mocks ├── IRMFailed.sol ├── IRMMax.sol ├── IRMOverBound.sol ├── IRMTestDefault.sol ├── IRMTestFixed.sol ├── IRMTestLinear.sol ├── IRMTestZero.sol ├── MockBalanceTracker.sol ├── MockDecimals.sol ├── MockEVault.sol ├── MockMinimalStatusCheck.sol ├── MockPriceOracle.sol ├── MockWrongEVC.sol ├── Permit2ECDSASigner.sol ├── ReentrancyAttack.sol └── TestERC20.sol ├── scripts ├── coverage.sh ├── echidna-assert.sh ├── echidna.sh └── medusa.sh └── unit ├── esr ├── ESR.Fuzz.t.sol ├── ESR.General.t.sol ├── ESR.Gulp.t.sol └── lib │ ├── ESRTest.sol │ └── MockToken.sol ├── esvault ├── ESVault.allocate.t.sol ├── ESVault.hookedOps.t.sol ├── ESVault.interestFee.t.sol └── ESVaultTestBase.t.sol ├── esynth ├── ESynth.totalSupply.t.sol ├── ESynthGeneral.t.sol └── lib │ └── ESynthTest.sol ├── evault ├── DToken.t.sol ├── Dispatch.t.sol ├── EVaultTestBase.t.sol ├── InvariantOverrides.sol ├── POC.t.sol ├── modules │ ├── BalanceForwarder │ │ ├── control.t.sol │ │ └── hooks.t.sol │ ├── Governance │ │ ├── convertFees.t.sol │ │ ├── disabledOpsAndFlags.t.sol │ │ ├── governorOnly.t.sol │ │ ├── hookedOps.t.sol │ │ ├── interestFee.t.sol │ │ ├── interestRates.t.sol │ │ ├── reserves.t.sol │ │ └── views.t.sol │ ├── Initialize │ │ └── errors.t.sol │ ├── Liquidation │ │ ├── basic.t.sol │ │ └── full.t.sol │ ├── Token │ │ ├── actions.t.sol │ │ └── views.t.sol │ └── Vault │ │ ├── amountLimits.t.sol │ │ ├── balancesNoInterest.t.sol │ │ ├── balancesWithInterest.t.sol │ │ ├── batch.t.sol │ │ ├── borrow.t.sol │ │ ├── borrowBasic.t.sol │ │ ├── borrowIsolation.t.sol │ │ ├── caps.t.sol │ │ ├── conversion.t.sol │ │ ├── decimals.t.sol │ │ ├── deposit.t.sol │ │ ├── flashloan.t.sol │ │ ├── liquidity.t.sol │ │ ├── ltv.t.sol │ │ ├── maliciousToken.t.sol │ │ ├── nested.t.sol │ │ ├── pullDebt.t.sol │ │ ├── repayWithShares.sol │ │ ├── skim.t.sol │ │ ├── transferShares.t.sol │ │ ├── views.t.sol │ │ └── withdraw.t.sol ├── protocolConfig.t.sol └── shared │ ├── AssetTransfers.t.sol │ ├── CustomTypes.t.sol │ ├── EVCClient.t.sol │ └── Reentrancy.t.sol ├── factory └── GenericFactory.t.sol ├── irm ├── IRMLimit.t.sol ├── IRMSynth.t.sol └── InterestRateLinearKink.t.sol └── pegStabilityModules └── PSM.t.sol /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Specify the owners of the code in the src directory 2 | src/ @dglowinski @kasperpawlowski @hoytech 3 | 4 | # Specify the owners of the code in the certora directory 5 | certora/ @dglowinski @kasperpawlowski @hoytech 6 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | submodules: recursive 16 | 17 | - name: Install foundry 18 | uses: foundry-rs/foundry-toolchain@v1 19 | with: 20 | version: nightly 21 | 22 | - name: Run foundry build 23 | run: | 24 | forge --version 25 | forge build 26 | id: build 27 | 28 | lint-check: 29 | runs-on: ubuntu-latest 30 | needs: build 31 | steps: 32 | - uses: actions/checkout@v3 33 | with: 34 | submodules: recursive 35 | - uses: foundry-rs/foundry-toolchain@v1 36 | with: 37 | version: nightly 38 | 39 | - name: Run foundry fmt check 40 | run: | 41 | forge fmt --check 42 | id: fmt 43 | 44 | test: 45 | runs-on: ubuntu-latest 46 | needs: lint-check 47 | steps: 48 | - uses: actions/checkout@v3 49 | with: 50 | submodules: recursive 51 | - uses: foundry-rs/foundry-toolchain@v1 52 | with: 53 | version: nightly 54 | - name: Run foundry tests 55 | # --ast tests enables inline configs to work https://github.com/foundry-rs/foundry/issues/7310#issuecomment-1978088200 56 | run: | 57 | forge test -vv --gas-report --ast 58 | id: test 59 | 60 | overrides: 61 | runs-on: ubuntu-latest 62 | needs: lint-check 63 | steps: 64 | - uses: actions/checkout@v3 65 | with: 66 | submodules: recursive 67 | - uses: foundry-rs/foundry-toolchain@v1 68 | with: 69 | version: nightly 70 | - name: Run foundry overrides test mode 71 | # --ast tests enables inline configs to work https://github.com/foundry-rs/foundry/issues/7310#issuecomment-1978088200 72 | run: | 73 | DEPLOY_OVERRIDES=true forge test -vv --gas-report --ast 74 | id: overrides 75 | 76 | coverage: 77 | runs-on: ubuntu-latest 78 | needs: lint-check 79 | steps: 80 | - uses: actions/checkout@v3 81 | with: 82 | submodules: recursive 83 | - uses: foundry-rs/foundry-toolchain@v1 84 | with: 85 | version: nightly 86 | - name: Run foundry coverage 87 | run: | 88 | FOUNDRY_PROFILE=coverage forge coverage --report summary 89 | id: coverage 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Dotenv file 11 | .env 12 | .vscode 13 | lcov.info 14 | 15 | # Invariant testing files 16 | crytic-export/ 17 | _corpus/ 18 | 19 | # System Files 20 | .DS_Store 21 | 22 | # Coverage directory 23 | coverage/ 24 | .certora_internal/ 25 | *.info -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/permit2"] 5 | path = lib/permit2 6 | url = https://github.com/Uniswap/permit2 7 | [submodule "lib/openzeppelin-contracts"] 8 | path = lib/openzeppelin-contracts 9 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 10 | [submodule "lib/ethereum-vault-connector"] 11 | path = lib/ethereum-vault-connector 12 | url = https://github.com/euler-xyz/ethereum-vault-connector 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Euler Vault Kit 2 | 3 | The Euler Vault Kit is a system for constructing credit vaults. Credit vaults are ERC-4626 vaults with added borrowing functionality. Unlike typical ERC-4626 vaults which earn yield by actively investing deposited funds, credit vaults are passive lending pools. See the [whitepaper](https://docs.euler.finance/euler-vault-kit-white-paper/) for more details. 4 | 5 | ## Install 6 | 7 | To install Euler Vault Kit in a [Foundry](https://github.com/foundry-rs/foundry) project: 8 | 9 | ```sh 10 | forge install euler-xyz/euler-vault-kit 11 | ``` 12 | 13 | ## Usage 14 | 15 | To install Foundry: 16 | 17 | ```sh 18 | curl -L https://foundry.paradigm.xyz | bash 19 | ``` 20 | 21 | This will download foundryup. To start Foundry, run: 22 | 23 | ```sh 24 | foundryup 25 | ``` 26 | 27 | To clone the repo: 28 | 29 | ```sh 30 | git clone https://github.com/euler-xyz/euler-vault-kit.git && cd euler-vault-kit 31 | ``` 32 | 33 | ## Testing 34 | 35 | ### in `default` mode 36 | 37 | To run the tests in a `default` mode: 38 | 39 | ```sh 40 | forge test 41 | ``` 42 | 43 | ### in `coverage` mode 44 | 45 | ```sh 46 | ./test/scripts/coverage.sh 47 | ``` 48 | 49 | ### invariants tests (`/tests/invariants`) 50 | ```sh 51 | ./test/scripts/echidna.sh # property mode 52 | ./test/scripts/echidna-assert.sh # assertion mode 53 | ./test/scripts/medusa.sh 54 | ``` 55 | 56 | ## Safety 57 | 58 | This software is **experimental** and is provided "as is" and "as available". 59 | 60 | **No warranties are provided** and **no liability will be accepted for any loss** incurred through the use of this codebase. 61 | 62 | Always include thorough tests when using the Euler Vault Kit to ensure it interacts correctly with your code. 63 | 64 | ## Known limitations 65 | 66 | Refer to the [whitepaper](https://docs.euler.finance/euler-vault-kit-white-paper/) for a list of known limitations and security considerations. 67 | 68 | ## License 69 | 70 | (c) 2024 Euler Labs Ltd. 71 | 72 | The Euler Vault Kit code is licensed under GPL-2.0 or later except for the files in `src/EVault/modules/`, which are licensed under Business Source License 1.1 (see the file `LICENSE`). These files will be automatically re-licensed under GPL-2.0 or later on April 24th, 2029. 73 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Euler Security Policy 2 | 3 | ## Vulnerability Disclosure and Bug Bounty 4 | 5 | Security is a top priority at Euler, and we engage in regular security reviews and have an active bug bounty program to ensure the integrity of our systems. 6 | 7 | To report a vulnerability, **please submit it through our bug bounty program**: 8 | [Euler Bug Bounty](https://euler.finance/bug-bounty) 9 | 10 | **Reports sent via email will not be accepted.** Email should only be used for general security inquiries. 11 | 12 | ## Security Team Contact Details 13 | 14 | For security-related questions or inquiries (not vulnerability reports), you can contact us via: 15 | - **Email**: [security@euler.xyz](mailto:security@euler.xyz) 16 | - **PGP Encryption**: [Euler Public Key](https://euler.finance/.well-known/public-key.asc) 17 | 18 | ## Previous Security Reviews 19 | 20 | Euler undergoes regular security audits. You can find details of previous security reviews here: 21 | [Euler Security Reviews](https://docs.euler.finance/security/security-reviews) 22 | 23 | ## Preferred Languages 24 | 25 | We accept security-related inquiries in **English (en)** 26 | -------------------------------------------------------------------------------- /audits/Cantina_competition_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-vault-kit/5b98b42048ba11ae82fb62dfec06d1010c8e41e6/audits/Cantina_competition_report.pdf -------------------------------------------------------------------------------- /audits/Certora_EVK_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-vault-kit/5b98b42048ba11ae82fb62dfec06d1010c8e41e6/audits/Certora_EVK_report.pdf -------------------------------------------------------------------------------- /audits/ChainSecurity_EVK_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-vault-kit/5b98b42048ba11ae82fb62dfec06d1010c8e41e6/audits/ChainSecurity_EVK_report.pdf -------------------------------------------------------------------------------- /audits/Electisec_setLTV_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-vault-kit/5b98b42048ba11ae82fb62dfec06d1010c8e41e6/audits/Electisec_setLTV_report.pdf -------------------------------------------------------------------------------- /audits/EnigmaDark_EVK_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-vault-kit/5b98b42048ba11ae82fb62dfec06d1010c8e41e6/audits/EnigmaDark_EVK_report.pdf -------------------------------------------------------------------------------- /audits/M4rio.eth_setLTV_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-vault-kit/5b98b42048ba11ae82fb62dfec06d1010c8e41e6/audits/M4rio.eth_setLTV_report.pdf -------------------------------------------------------------------------------- /audits/Omniscia_EVK_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-vault-kit/5b98b42048ba11ae82fb62dfec06d1010c8e41e6/audits/Omniscia_EVK_report.pdf -------------------------------------------------------------------------------- /audits/OpenZeppelin_EVK_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-vault-kit/5b98b42048ba11ae82fb62dfec06d1010c8e41e6/audits/OpenZeppelin_EVK_report.pdf -------------------------------------------------------------------------------- /audits/OpenZeppelin_Synths_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-vault-kit/5b98b42048ba11ae82fb62dfec06d1010c8e41e6/audits/OpenZeppelin_Synths_report.pdf -------------------------------------------------------------------------------- /audits/Spearbit_EVK_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-vault-kit/5b98b42048ba11ae82fb62dfec06d1010c8e41e6/audits/Spearbit_EVK_report.pdf -------------------------------------------------------------------------------- /audits/yAudit_EVK_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-vault-kit/5b98b42048ba11ae82fb62dfec06d1010c8e41e6/audits/yAudit_EVK_report.pdf -------------------------------------------------------------------------------- /audits/yAudit_competition_fixes_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/euler-vault-kit/5b98b42048ba11ae82fb62dfec06d1010c8e41e6/audits/yAudit_competition_fixes_report.pdf -------------------------------------------------------------------------------- /certora/conf/Cache.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/CacheHarness.sol" 4 | ], 5 | "verify": "CacheHarness:certora/specs/Cache.spec", 6 | "solc": "solc8.24", 7 | "packages": [ 8 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 9 | "forge-std=lib/forge-std/src" 10 | ], 11 | "prover_version": "master", 12 | "server" : "production", 13 | "parametric_contracts": ["CacheHarness"], 14 | "optimistic_loop": true, 15 | "loop_iter": "2", 16 | "solc_via_ir": true, 17 | "solc_optimize": "10000", 18 | "rule_sanity": "basic", 19 | "function_finder_mode" : "relaxed", 20 | "finder_friendly_optimizer" : false, 21 | "prover_args": [ 22 | "-smt_nonLinearArithmetic true", 23 | "-adaptiveSolverConfig false", 24 | "-solvers [z3:def{randomSeed=1},z3:def{randomSeed=2},z3:def{randomSeed=3},z3:def{randomSeed=4},z3:def{randomSeed=5},z3:def{randomSeed=6},z3:def{randomSeed=7},z3:def{randomSeed=8},z3:def{randomSeed=9},z3:def{randomSeed=10}]" 25 | ], 26 | "smt_timeout": "7000" 27 | } -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/README.md: -------------------------------------------------------------------------------- 1 | To run all of these conf files easily, use the certora/scripts/runERC4626RulesSplitConfs.py -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-assetsMoreThanSupply.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "rule": ["assetsMoreThanSupply",], 20 | "parametric_contracts": ["ERC4626Harness"], 21 | "build_cache": true, 22 | "prover_args": [ 23 | "-maxConcurrentTransforms INLINED_HOOKS:8", 24 | "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -splitParallel true -splitParallelInitialDepth 2 -depth 15 -s [z3:arith1,yices:def] -mediumTimeout 2 -splitParallelTimelimit 7200" 25 | ], 26 | "prover_version" : "master", 27 | // Performance tuning options below this line 28 | "solc_via_ir": true, 29 | "solc_optimize": "10000", 30 | "optimistic_loop": true, 31 | "loop_iter": "2", 32 | "smt_timeout": "7200" 33 | } 34 | 35 | -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-convertToAssetsWeakAdditivity.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "rule": ["convertToAssetsWeakAdditivity"], 20 | "parametric_contracts": ["ERC4626Harness"], 21 | "build_cache": true, 22 | "prover_version" : "master", 23 | // Performance tuning options below this line 24 | "solc_via_ir": true, 25 | "solc_optimize": "10000", 26 | "optimistic_loop": true, 27 | "loop_iter": "2", 28 | "disable_auto_cache_key_gen": true, 29 | "fe_version": "latest", 30 | } 31 | 32 | -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-convertToSharesWeakAdditivity.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "rule": ["convertToSharesWeakAdditivity"], 20 | "parametric_contracts": ["ERC4626Harness"], 21 | "build_cache": true, 22 | "prover_version" : "master", 23 | // Performance tuning options below this line 24 | "solc_via_ir": true, 25 | "solc_optimize": "10000", 26 | "optimistic_loop": true, 27 | "loop_iter": "2", 28 | "disable_auto_cache_key_gen": true, 29 | "fe_version": "latest", 30 | } 31 | 32 | -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-depositMonotonicity.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "rule": ["depositMonotonicity",], 20 | "parametric_contracts": ["ERC4626Harness"], 21 | "build_cache": true, 22 | "prover_version" : "master", 23 | // Performance tuning options below this line 24 | "solc_via_ir": true, 25 | "solc_optimize": "10000", 26 | "optimistic_loop": true, 27 | "loop_iter": "2", 28 | "disable_auto_cache_key_gen": true, 29 | "fe_version": "latest", 30 | "smt_timeout": "7200" 31 | } 32 | 33 | -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-dustFavorsTheHouse.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "rule": ["dustFavorsTheHouse",], 20 | "parametric_contracts": ["ERC4626Harness"], 21 | "build_cache": true, 22 | "prover_version" : "master", 23 | // Performance tuning options below this line 24 | "solc_via_ir": true, 25 | "solc_optimize": "10000", 26 | "optimistic_loop": true, 27 | "loop_iter": "2", 28 | "disable_auto_cache_key_gen": true, 29 | "fe_version": "latest", 30 | "smt_timeout": "7200" 31 | } 32 | 33 | -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-dustFavorsTheHouseAssets.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "rule": ["dustFavorsTheHouseAssets",], 20 | "parametric_contracts": ["ERC4626Harness"], 21 | "build_cache": true, 22 | "prover_version" : "master", 23 | // Performance tuning options below this line 24 | "solc_via_ir": true, 25 | "solc_optimize": "10000", 26 | "optimistic_loop": true, 27 | "loop_iter": "2", 28 | "disable_auto_cache_key_gen": true, 29 | "fe_version": "latest", 30 | "smt_timeout": "7200" 31 | } 32 | 33 | -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-noAssetsIfNoSupply.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "rule": ["noAssetsIfNoSupply",], 20 | "parametric_contracts": ["ERC4626Harness"], 21 | "build_cache": true, 22 | "prover_version" : "master", 23 | // Performance tuning options below this line 24 | "solc_via_ir": true, 25 | "solc_optimize": "10000", 26 | "optimistic_loop": true, 27 | "loop_iter": "2", 28 | "disable_auto_cache_key_gen": true, 29 | "fe_version": "latest", 30 | "prover_args": [ 31 | "-maxConcurrentTransforms INLINED_HOOKS:6", 32 | "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0 " 33 | ], 34 | "smt_timeout": "7200" 35 | } 36 | 37 | -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-noSupplyIfNoAssets.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "rule": ["noSupplyIfNoAssets",], 20 | "parametric_contracts": ["ERC4626Harness"], 21 | "build_cache": true, 22 | "prover_version" : "master", 23 | // Performance tuning options below this line 24 | "solc_via_ir": true, 25 | "solc_optimize": "10000", 26 | "optimistic_loop": true, 27 | "loop_iter": "2", 28 | "disable_auto_cache_key_gen": true, 29 | "fe_version": "latest", 30 | "smt_timeout": "7200" 31 | } 32 | 33 | -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-onlyContributionMethodsReduce.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "prover_args": [ 20 | "-maxConcurrentTransforms INLINED_HOOKS:8" 21 | ], 22 | "rule": ["onlyContributionMethodsReduceAssets"], 23 | "parametric_contracts": ["ERC4626Harness"], 24 | "build_cache": true, 25 | "prover_version" : "master", 26 | // Performance tuning options below this line 27 | "solc_via_ir": true, 28 | "solc_optimize": "10000", 29 | "optimistic_loop": true, 30 | "loop_iter": "2", 31 | "smt_timeout": "7200" 32 | } 33 | 34 | -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-totalsMonotonicity.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "prover_args": [ 20 | "-maxConcurrentTransforms INLINED_HOOKS:8" 21 | ], 22 | "rule": ["totalsMonotonicity"], 23 | "parametric_contracts": ["ERC4626Harness"], 24 | "build_cache": true, 25 | "prover_version" : "master", 26 | // Performance tuning options below this line 27 | "solc_via_ir": true, 28 | "solc_optimize": "10000", 29 | "optimistic_loop": true, 30 | "loop_iter": "2", 31 | "smt_timeout": "7200" 32 | } 33 | 34 | -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-most.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "rule": ["vaultSolvency"], 20 | "parametric_contracts": ["ERC4626Harness"], 21 | "build_cache": true, 22 | "server": "staging", // 10 hour queue 23 | "prover_version" : "master", 24 | // Performance tuning options below this line 25 | "solc_via_ir": true, 26 | "solc_optimize": "10000", 27 | "optimistic_loop": true, 28 | "loop_iter": "2", 29 | "prover_args" : [ 30 | "-smt_nonLinearArithmetic false", 31 | ], 32 | // "smt_timeout": "7200", 33 | "smt_timeout": "28800", 34 | } 35 | 36 | -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-redeem.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "rule": ["vaultSolvency"], 20 | "method": "redeem(uint256,address,address)", 21 | "parametric_contracts": ["ERC4626Harness"], 22 | "build_cache": true, 23 | "server": "staging", // 10 hour queue 24 | "prover_version" : "master", 25 | // Performance tuning options below this line 26 | "solc_via_ir": true, 27 | "solc_optimize": "10000", 28 | "optimistic_loop": true, 29 | "loop_iter": "2", 30 | "prover_args" : [ 31 | "-smt_nonLinearArithmetic false", 32 | ], 33 | // "smt_timeout": "7200", 34 | "smt_timeout": "28800", 35 | } 36 | 37 | -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw-as-rule.conf: -------------------------------------------------------------------------------- 1 | { 2 | "build_cache": true, 3 | "files": [ 4 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 5 | "src/EVault/modules/Vault.sol", 6 | "certora/helpers/DummyERC20A.sol", 7 | // "certora/helpers/DummyERC20B.sol", 8 | "certora/harness/EVCHarness.sol", 9 | "certora/harness/BaseHarness.sol", 10 | "certora/harness/ERC4626Harness.sol" 11 | ], 12 | "loop_iter": "2", 13 | "msg": " -vaultSolvency-withdraw : rerun ERC4626 for wrap up", 14 | "optimistic_loop": true, 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "parametric_contracts": [ 20 | "ERC4626Harness" 21 | ], 22 | "process": "emv", 23 | "prover_args": [ 24 | "-smt_nonLinearArithmetic true", 25 | "-adaptiveSolverConfig false", 26 | "-deleteSMTFile false", 27 | "-depth 20", 28 | ], 29 | "prover_version" : "master", 30 | "rule": [ 31 | "vaultSolvencyWithdraw_totals", 32 | "vaultSolvencyWithdraw_underlying", 33 | ], 34 | "rule_sanity": "basic", 35 | "server": "production", 36 | "smt_timeout": "7800", 37 | "solc": "solc8.24", 38 | "solc_optimize": "10000", 39 | "solc_via_ir": true, 40 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec" 41 | } -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626-vaultSolvency-withdraw.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "rule": ["vaultSolvency"], 20 | "method": "withdraw(uint256,address,address)", 21 | "parametric_contracts": ["ERC4626Harness"], 22 | "build_cache": true, 23 | "server": "staging", // 10 hour queue 24 | "prover_version" : "master", 25 | // Performance tuning options below this line 26 | "solc_via_ir": true, 27 | "solc_optimize": "10000", 28 | "optimistic_loop": true, 29 | "loop_iter": "2", 30 | "prover_args": [ 31 | "-smt_nonLinearArithmetic true -adaptiveSolverConfig false -depth 0" 32 | ], 33 | // "smt_timeout": "7200", 34 | "smt_timeout": "28800", 35 | } 36 | 37 | -------------------------------------------------------------------------------- /certora/conf/ERC4626Split/VaultERC4626.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/EthereumVaultConnector.sol", 4 | "src/EVault/modules/Vault.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyERC20B.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/BaseHarness.sol", 9 | "certora/harness/ERC4626Harness.sol", 10 | ], 11 | "verify": "ERC4626Harness:certora/specs/VaultERC4626.spec", 12 | "solc": "solc8.24", 13 | "rule_sanity": "basic", 14 | "msg": "Vault ERC4626", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "parametric_contracts": ["ERC4626Harness"], 20 | "build_cache": true, 21 | "prover_version" : "master", 22 | // Performance tuning options below this line 23 | "solc_via_ir": true, 24 | "solc_optimize": "10000", 25 | "optimistic_loop": true, 26 | "loop_iter": "2", 27 | "disable_auto_cache_key_gen": true, 28 | "fe_version": "latest", 29 | "smt_timeout": "7200" 30 | } 31 | 32 | -------------------------------------------------------------------------------- /certora/conf/EVault/modules/BalanceForwarder.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 4 | "certora/helpers/DummyERC20A.sol", 5 | "certora/harness/EVCHarness.sol", 6 | "src/EVault/modules/BalanceForwarder.sol", 7 | "certora/harness/BaseHarness.sol", 8 | "certora/harness/modules/BalanceForwarderHarness.sol", 9 | ], 10 | "link": [ 11 | "BalanceForwarderHarness:evc=EVCHarness", 12 | ], 13 | "verify": "BalanceForwarderHarness:certora/specs/BalanceForwarder.spec", 14 | "solc": "solc8.24", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "prover_version": "master", 20 | "server" : "production", 21 | "parametric_contracts": ["BalanceForwarderHarness"], 22 | "build_cache": true, 23 | "prover_args": ["-smt_bitVectorTheory", "true"], 24 | "optimistic_loop": true, 25 | "loop_iter": "2", 26 | "solc_via_ir": true, 27 | "solc_optimize": "10000", 28 | "rule_sanity": "basic", 29 | "function_finder_mode" : "relaxed", 30 | "finder_friendly_optimizer" : false, 31 | } -------------------------------------------------------------------------------- /certora/conf/EVault/modules/Governance.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 4 | "certora/helpers/DummyERC20A.sol", 5 | "src/ProtocolConfig/ProtocolConfig.sol", 6 | "src/EVault/modules/Governance.sol", 7 | "certora/harness/BaseHarness.sol", 8 | "certora/harness/EVCHarness.sol", 9 | "certora/harness/modules/GovernanceHarness.sol", 10 | ], 11 | "link": [ 12 | "GovernanceHarness:protocolConfig=ProtocolConfig", 13 | "GovernanceHarness:evc=EVCHarness" 14 | ], 15 | "verify": "GovernanceHarness:certora/specs/Governance.spec", 16 | "parametric_contracts": ["GovernanceHarness"], 17 | "solc": "solc8.24", 18 | "rule_sanity": "basic", 19 | "msg": "Governance benchmarking", 20 | "packages": [ 21 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 22 | "forge-std=lib/forge-std/src" 23 | ], 24 | "prover_version": "master", 25 | "build_cache": true, 26 | // Performance tuning options below this line 27 | "solc_via_ir": true, 28 | "solc_optimize": "10000", 29 | "optimistic_loop": true, 30 | "loop_iter": "2", 31 | "smt_timeout":"7200", 32 | } -------------------------------------------------------------------------------- /certora/conf/EVault/modules/Liquidation.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 4 | "certora/helpers/DummyERC20A.sol", 5 | "src/EVault/modules/Liquidation.sol", 6 | "certora/harness/BaseHarness.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/modules/LiquidationHarness.sol" 9 | ], 10 | "link": [ 11 | "LiquidationHarness:evc=EVCHarness", 12 | ], 13 | "verify": "LiquidationHarness:certora/specs/Liquidation.spec", 14 | "solc": "solc8.24", 15 | "msg": "Liquidation benchmarking", 16 | "packages": [ 17 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 18 | "forge-std=lib/forge-std/src" 19 | ], 20 | "parametric_contracts": ["LiquidationHarness"], 21 | "rule_sanity": "basic", 22 | "prover_version": "master", 23 | "server" : "production", 24 | // "coverage_info" : "advanced", 25 | "build_cache": true, 26 | // Performance tuing options below this line 27 | "prover_args": [ 28 | "-depth 10", 29 | "-smt_nonLinearArithmetic true", 30 | "-adaptiveSolverConfig false" 31 | ], 32 | "function_finder_mode": "relaxed", 33 | "optimistic_loop": true, 34 | "loop_iter": "2", 35 | "solc_via_ir": true, 36 | "solc_optimize": "10000", 37 | "smt_timeout": "7000" 38 | } -------------------------------------------------------------------------------- /certora/conf/EVault/modules/RiskManager.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 4 | "certora/helpers/DummyERC20A.sol", 5 | "src/EVault/modules/RiskManager.sol", 6 | "certora/harness/BaseHarness.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/modules/RiskManagerHarness.sol", 9 | ], 10 | "link": [ 11 | "RiskManagerHarness:evc=EVCHarness", 12 | ], 13 | "verify": "RiskManagerHarness:certora/specs/RiskManager.spec", 14 | "solc": "solc8.24", 15 | "packages": [ 16 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 17 | "forge-std=lib/forge-std/src" 18 | ], 19 | "parametric_contracts": ["RiskManagerHarness"], 20 | "rule_sanity": "basic", 21 | "server": "production", 22 | "build_cache": true, 23 | // "coverage_info" : "advanced", 24 | // Performance tuning options below this line 25 | "prover_args": [ 26 | "-depth 0", 27 | "-smt_nonLinearArithmetic true", 28 | "-adaptiveSolverConfig false" 29 | ], 30 | "function_finder_mode": "relaxed", 31 | "optimistic_loop": true, 32 | "loop_iter": "2", 33 | "solc_via_ir": true, 34 | "solc_optimize": "10000", 35 | "smt_timeout": "7000", 36 | "prover_version": "master" 37 | } -------------------------------------------------------------------------------- /certora/conf/EVault/modules/Vault.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 4 | "certora/helpers/DummyERC20A.sol", 5 | "src/EVault/modules/Vault.sol", 6 | "certora/harness/BaseHarness.sol", 7 | "certora/harness/EVCHarness.sol", 8 | "certora/harness/modules/VaultHarness.sol", 9 | ], 10 | "link" : [ 11 | "VaultHarness:evc=EVCHarness", 12 | ], 13 | "verify": "VaultHarness:certora/specs/Vault.spec", 14 | "solc": "solc8.24", 15 | "rule_sanity": "basic", 16 | "msg": "Vault", 17 | "packages": [ 18 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 19 | "forge-std=lib/forge-std/src" 20 | ], 21 | "parametric_contracts": ["VaultHarness"], 22 | "prover_version": "master", 23 | "build_cache" : true, 24 | // Performance tuning options below this line 25 | "solc_via_ir": true, 26 | "solc_optimize": "10000", 27 | "optimistic_loop": true, 28 | "loop_iter": "2", 29 | } -------------------------------------------------------------------------------- /certora/conf/healthStatus/BalanceForwarderHealthStatus.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EVCHarness.sol", 4 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyETokenA.sol", 7 | "certora/helpers/DummyETokenB.sol", 8 | "src/EVault/modules/BalanceForwarder.sol", 9 | "certora/harness/BaseHarness.sol", 10 | "certora/harness/healthStatus/BalanceForwarderHSHarness.sol", 11 | ], 12 | "link": [ 13 | "BalanceForwarderHSHarness:evc=EVCHarness", 14 | ], 15 | "verify": "BalanceForwarderHSHarness:certora/specs/HealthStatusInvariant.spec", 16 | "solc": "solc8.24", 17 | "packages": [ 18 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 19 | "forge-std=lib/forge-std/src" 20 | ], 21 | "rule" : [ 22 | "accountsStayHealthy_strategy" 23 | ], 24 | "build_cache": true, 25 | "prover_version": "master", 26 | "server" : "staging", 27 | "parametric_contracts": ["BalanceForwarderHSHarness"], 28 | "optimistic_loop": true, 29 | "loop_iter": "2", 30 | "solc_via_ir": true, 31 | "solc_optimize": "10000", 32 | "rule_sanity": "basic", 33 | "function_finder_mode" : "relaxed", 34 | "finder_friendly_optimizer" : false, 35 | "prover_args": [ 36 | "-splitParallel true", 37 | "-deleteSMTFile false", 38 | "-smt_easy_LIA true" 39 | ], 40 | "smt_timeout": "4800", 41 | } -------------------------------------------------------------------------------- /certora/conf/healthStatus/BorrowingHealthStatus.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EVCHarness.sol", 4 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyETokenA.sol", 7 | "certora/helpers/DummyETokenB.sol", 8 | "src/EVault/modules/Vault.sol", 9 | "certora/harness/BaseHarness.sol", 10 | "certora/harness/healthStatus/BorrowingHSHarness.sol", 11 | ], 12 | "link": [ 13 | "BorrowingHSHarness:evc=EVCHarness" 14 | ], 15 | "verify": "BorrowingHSHarness:certora/specs/HealthStatusInvariant.spec", 16 | "solc": "solc8.24", 17 | "packages": [ 18 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 19 | "forge-std=lib/forge-std/src" 20 | ], 21 | "rule" : [ 22 | "accountsStayHealthy_strategy" 23 | ], 24 | // "method" : "borrow(uint256,address)", 25 | // "method" : "pullDebt(uint256,address)", 26 | // "method" : "repayWithShares(uint256,address)", 27 | // "method" : "repay(uint256,address)", 28 | "build_cache": true, 29 | "prover_version": "master", 30 | "server" : "staging", 31 | "parametric_contracts": ["BorrowingHSHarness"], 32 | "optimistic_loop": true, 33 | "loop_iter": "2", 34 | "solc_via_ir": true, 35 | "solc_optimize": "10000", 36 | "rule_sanity": "basic", 37 | "function_finder_mode" : "relaxed", 38 | "finder_friendly_optimizer" : false, 39 | "prover_args": [ 40 | "-smt_easy_LIA true" 41 | ], 42 | "smt_timeout": "28800", 43 | } -------------------------------------------------------------------------------- /certora/conf/healthStatus/ETokenCollateralHealthStatus.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EVCHarness.sol", 4 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyETokenA.sol", 7 | "certora/helpers/DummyETokenB.sol", 8 | "src/EVault/modules/BalanceForwarder.sol", 9 | "certora/harness/BaseHarness.sol", 10 | "certora/harness/healthStatus/BalanceForwarderHSHarness.sol", 11 | ], 12 | "link": [ 13 | "BalanceForwarderHSHarness:evc=EVCHarness", 14 | "DummyETokenA:evc=EVCHarness", 15 | ], 16 | "verify": "BalanceForwarderHSHarness:certora/specs/HealthStatusInvariant.spec", 17 | "solc": "solc8.24", 18 | "packages": [ 19 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 20 | "forge-std=lib/forge-std/src" 21 | ], 22 | "rule" : [ 23 | "accountsStayHealthy_strategy" 24 | ], 25 | "build_cache": true, 26 | "prover_version": "master", 27 | "server" : "staging", 28 | "parametric_contracts": ["DummyETokenA"], 29 | "optimistic_loop": true, 30 | "loop_iter": "2", 31 | "solc_via_ir": true, 32 | "solc_optimize": "10000", 33 | "rule_sanity": "basic", 34 | "function_finder_mode" : "relaxed", 35 | "finder_friendly_optimizer" : false, 36 | // "prover_args": [ 37 | // "-splitParallel true", 38 | // "-deleteSMTFile false", 39 | // "-smt_easy_LIA true" 40 | // ], 41 | "smt_timeout": "28800", 42 | } -------------------------------------------------------------------------------- /certora/conf/healthStatus/GovernanceHealthStatus.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EVCHarness.sol", 4 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyETokenA.sol", 7 | "certora/helpers/DummyETokenB.sol", 8 | "src/EVault/modules/Vault.sol", 9 | "certora/harness/BaseHarness.sol", 10 | "certora/harness/healthStatus/GovernanceHSHarness.sol", 11 | ], 12 | "link": [ 13 | "GovernanceHSHarness:evc=EVCHarness", 14 | ], 15 | "verify": "GovernanceHSHarness:certora/specs/HealthStatusInvariant.spec", 16 | "solc": "solc8.24", 17 | "packages": [ 18 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 19 | "forge-std=lib/forge-std/src" 20 | ], 21 | "rule" : [ 22 | "accountsStayHealthy_strategy" 23 | ], 24 | "build_cache": true, 25 | "prover_version": "master", 26 | "server" : "staging", 27 | "parametric_contracts": ["GovernanceHSHarness"], 28 | // "prover_args": ["-smt_bitVectorTheory", "true"], 29 | "optimistic_loop": true, 30 | "loop_iter": "2", 31 | "solc_via_ir": true, 32 | "solc_optimize": "10000", 33 | "rule_sanity": "basic", 34 | "function_finder_mode" : "relaxed", 35 | "finder_friendly_optimizer" : false, 36 | "prover_args": [ 37 | "-splitParallel true", 38 | "-deleteSMTFile false", 39 | "-smt_easy_LIA true" 40 | ], 41 | "smt_timeout": "28800", 42 | } -------------------------------------------------------------------------------- /certora/conf/healthStatus/InitializeHealthStatus.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EVCHarness.sol", 4 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyETokenA.sol", 7 | "certora/helpers/DummyETokenB.sol", 8 | "src/EVault/modules/Vault.sol", 9 | "certora/harness/BaseHarness.sol", 10 | "certora/harness/healthStatus/InitializeHSHarness.sol", 11 | ], 12 | "link": [ 13 | "InitializeHSHarness:evc=EVCHarness", 14 | ], 15 | "verify": "InitializeHSHarness:certora/specs/HealthStatusInvariant.spec", 16 | "solc": "solc8.24", 17 | "packages": [ 18 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 19 | "forge-std=lib/forge-std/src" 20 | ], 21 | "rule" : [ 22 | "accountsStayHealthy_strategy" 23 | ], 24 | "build_cache": true, 25 | "prover_version": "master", 26 | "server" : "staging", 27 | "parametric_contracts": ["InitializeHSHarness"], 28 | "optimistic_loop": true, 29 | "loop_iter": "2", 30 | "solc_via_ir": true, 31 | "solc_optimize": "10000", 32 | "rule_sanity": "basic", 33 | "function_finder_mode" : "relaxed", 34 | "finder_friendly_optimizer" : false, 35 | "prover_args": [ 36 | "-splitParallel true", 37 | "-deleteSMTFile false", 38 | "-smt_easy_LIA true" 39 | ], 40 | "smt_timeout": "28800", 41 | } -------------------------------------------------------------------------------- /certora/conf/healthStatus/LiquidateHealthStatus.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EVCHarness.sol", 4 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyETokenA.sol", 7 | "certora/helpers/DummyETokenB.sol", 8 | "src/EVault/modules/Vault.sol", 9 | "certora/harness/BaseHarness.sol", 10 | "certora/harness/healthStatus/LiquidationHSHarness.sol", 11 | ], 12 | "link": [ 13 | "LiquidationHSHarness:evc=EVCHarness", 14 | ], 15 | "verify": "LiquidationHSHarness:certora/specs/LiquidateHealthStatus.spec", 16 | "solc": "solc8.24", 17 | "packages": [ 18 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 19 | "forge-std=lib/forge-std/src" 20 | ], 21 | "rule" : [ 22 | "liquidateAccountsStayHealthy_liquidator_no_debt_socialization", 23 | "liquidateAccountsStayHealthy_liquidator_with_debt_socialization", 24 | "liquidateAccountsStayHealthy_not_violator" 25 | ], 26 | "method":"liquidate(address,address,uint256,uint256)", 27 | "build_cache": true, 28 | "prover_version": "master", 29 | "server" : "staging", 30 | "parametric_contracts": ["LiquidationHSHarness"], 31 | "optimistic_loop": true, 32 | "loop_iter": "2", 33 | "solc_via_ir": true, 34 | "solc_optimize": "10000", 35 | "rule_sanity": "basic", 36 | "function_finder_mode" : "relaxed", 37 | "finder_friendly_optimizer" : false, 38 | "prover_args": [ 39 | "-smt_easy_LIA true" 40 | ], 41 | "smt_timeout": "28800", 42 | } -------------------------------------------------------------------------------- /certora/conf/healthStatus/LiquidationHealthStatus.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EVCHarness.sol", 4 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyETokenA.sol", 7 | "certora/helpers/DummyETokenB.sol", 8 | "src/EVault/modules/Vault.sol", 9 | "certora/harness/BaseHarness.sol", 10 | "certora/harness/healthStatus/LiquidationHSHarness.sol", 11 | ], 12 | "link": [ 13 | "LiquidationHSHarness:evc=EVCHarness", 14 | ], 15 | "verify": "LiquidationHSHarness:certora/specs/HealthStatusInvariant.spec", 16 | "solc": "solc8.24", 17 | "packages": [ 18 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 19 | "forge-std=lib/forge-std/src" 20 | ], 21 | "rule" : [ 22 | "accountsStayHealthy_strategy" 23 | ], 24 | "build_cache": true, 25 | "prover_version": "master", 26 | "server" : "staging", 27 | "parametric_contracts": ["LiquidationHSHarness"], 28 | "optimistic_loop": true, 29 | "loop_iter": "2", 30 | "solc_via_ir": true, 31 | "solc_optimize": "10000", 32 | "rule_sanity": "basic", 33 | "function_finder_mode" : "relaxed", 34 | "finder_friendly_optimizer" : false, 35 | "prover_args": [ 36 | " -smt_easy_LIA true" 37 | ], 38 | "smt_timeout": "28800", 39 | } -------------------------------------------------------------------------------- /certora/conf/healthStatus/README.md: -------------------------------------------------------------------------------- 1 | Note that for most modules the spec HealthStatusInvariant.spec is used, but for Liquidation, 2 | the spec needs to be split into more cases for performance reasons so it uses LiquidateHealthStatus.spec 3 | 4 | Also note that for ETokenCollateralHealthStatus is used to verify functions called on the collateral 5 | EToken contract rather than the vault under test, and UnderlyingTokenHealthStatus is used to verify 6 | functions called on the underlying asset. 7 | 8 | To run all of these configurations easily, use certora/scripts/runHealthStatusAllModules.py -------------------------------------------------------------------------------- /certora/conf/healthStatus/TokenHealthStatus.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EVCHarness.sol", 4 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyETokenA.sol", 7 | "certora/helpers/DummyETokenB.sol", 8 | "src/EVault/modules/Vault.sol", 9 | "certora/harness/BaseHarness.sol", 10 | "certora/harness/healthStatus/TokenHSHarness.sol", 11 | ], 12 | "link": [ 13 | "TokenHSHarness:evc=EVCHarness", 14 | ], 15 | "verify": "TokenHSHarness:certora/specs/HealthStatusInvariant.spec", 16 | "solc": "solc8.24", 17 | "packages": [ 18 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 19 | "forge-std=lib/forge-std/src" 20 | ], 21 | "rule" : [ 22 | "accountsStayHealthy_strategy" 23 | ], 24 | "build_cache": true, 25 | "prover_version": "master", 26 | "server" : "staging", 27 | "parametric_contracts": ["TokenHSHarness"], 28 | "optimistic_loop": true, 29 | "loop_iter": "2", 30 | "solc_via_ir": true, 31 | "solc_optimize": "10000", 32 | "rule_sanity": "basic", 33 | "function_finder_mode" : "relaxed", 34 | "finder_friendly_optimizer" : false, 35 | "prover_args": [ 36 | "-splitParallel true", 37 | "-deleteSMTFile false", 38 | "-smt_easy_LIA true" 39 | ], 40 | "smt_timeout": "28800", 41 | } -------------------------------------------------------------------------------- /certora/conf/healthStatus/UnderlyingTokenHealthStatus.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EVCHarness.sol", 4 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyETokenA.sol", 7 | "certora/helpers/DummyETokenB.sol", 8 | "src/EVault/modules/BalanceForwarder.sol", 9 | "certora/harness/BaseHarness.sol", 10 | "certora/harness/healthStatus/BalanceForwarderHSHarness.sol", 11 | ], 12 | "link": [ 13 | "BalanceForwarderHSHarness:evc=EVCHarness", 14 | ], 15 | "verify": "BalanceForwarderHSHarness:certora/specs/HealthStatusInvariant.spec", 16 | "solc": "solc8.24", 17 | "packages": [ 18 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 19 | "forge-std=lib/forge-std/src" 20 | ], 21 | "rule" : [ 22 | "accountsStayHealthy_strategy" 23 | ], 24 | "build_cache": true, 25 | "prover_version": "master", 26 | "server" : "staging", 27 | "parametric_contracts": ["DummyERC20A"], 28 | "optimistic_loop": true, 29 | "loop_iter": "2", 30 | "solc_via_ir": true, 31 | "solc_optimize": "10000", 32 | "rule_sanity": "basic", 33 | "function_finder_mode" : "relaxed", 34 | "finder_friendly_optimizer" : false, 35 | "prover_args": [ 36 | "-splitParallel true", 37 | "-deleteSMTFile false", 38 | "-smt_easy_LIA true" 39 | ], 40 | "smt_timeout": "4800", 41 | } -------------------------------------------------------------------------------- /certora/conf/healthStatus/VaultHealthStatus.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EVCHarness.sol", 4 | "lib/ethereum-vault-connector/src/ExecutionContext.sol", 5 | "certora/helpers/DummyERC20A.sol", 6 | "certora/helpers/DummyETokenA.sol", 7 | "certora/helpers/DummyETokenB.sol", 8 | "src/EVault/modules/Vault.sol", 9 | "src/EVault/modules/Token.sol", 10 | "certora/harness/BaseHarness.sol", 11 | "certora/harness/healthStatus/VaultHSHarness.sol", 12 | ], 13 | "link": [ 14 | "VaultHSHarness:evc=EVCHarness", 15 | ], 16 | "verify": "VaultHSHarness:certora/specs/HealthStatusInvariant.spec", 17 | "solc": "solc8.24", 18 | "packages": [ 19 | "ethereum-vault-connector=lib/ethereum-vault-connector/src", 20 | "forge-std=lib/forge-std/src" 21 | ], 22 | "rule" : [ 23 | "accountsStayHealthy_strategy", 24 | ], 25 | "build_cache": true, 26 | "prover_version": "master", 27 | "server" : "staging", 28 | "parametric_contracts": ["VaultHSHarness"], 29 | "optimistic_loop": true, 30 | "loop_iter": "2", 31 | "solc_via_ir": true, 32 | "solc_optimize": "10000", 33 | "rule_sanity": "basic", 34 | "function_finder_mode" : "relaxed", 35 | "finder_friendly_optimizer" : false, 36 | "prover_args": [ 37 | "-maxConcurrentTransforms INLINED_HOOKS:8", 38 | "-splitParallel true", 39 | "-deleteSMTFile false", 40 | "-smt_easy_LIA true" 41 | ], 42 | "smt_timeout": "28800", 43 | } -------------------------------------------------------------------------------- /certora/harness/BaseHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/EVault/shared/Base.sol"; 6 | import "../../certora/harness/AbstractBaseHarness.sol"; 7 | 8 | // This exists so that Base.LTVConfig and other type declarations 9 | // are available in CVL and can be used across specs for different modules. 10 | // We need to split this into a concrete contract and an Abstract contract 11 | // so that we can refer to Base.LTVConfig as a type in shared CVL functions 12 | // while also making function definitions sharable among harnesses via 13 | // AbstractBase. 14 | contract BaseHarness is Base, AbstractBaseHarness { 15 | constructor(Integrations memory integrations) Base(integrations) {} 16 | } -------------------------------------------------------------------------------- /certora/harness/CacheHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/EVault/shared/Cache.sol"; 6 | 7 | contract CacheHarness is Cache { 8 | function updateVaultExt() external virtual returns (VaultCache memory vaultCache) { 9 | updateVault(); 10 | } 11 | function initVaultCacheExt(VaultCache memory vaultCache) external view returns (bool dirty) { 12 | return initVaultCache(vaultCache); 13 | } 14 | function getlastInterestAccumulatorUpdate() external view returns (uint256) { 15 | return vaultStorage.lastInterestAccumulatorUpdate; 16 | } 17 | function getTotalBorrows() external view returns (Owed) { 18 | return vaultStorage.totalBorrows; 19 | } 20 | function getInterestAcc() external view returns (uint256) { 21 | return vaultStorage.interestAccumulator; 22 | } 23 | function getAccumulatedFees() external view returns (Shares) { 24 | return vaultStorage.accumulatedFees; 25 | } 26 | function getTotalShares() external view returns (Shares) { 27 | return vaultStorage.totalShares; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /certora/harness/ERC4626Harness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | import "../../certora/harness/AbstractBaseHarness.sol"; 5 | import "../../src/EVault/modules/Vault.sol"; 6 | import "../../src/EVault/modules/Token.sol"; 7 | 8 | contract ERC4626Harness is VaultModule, TokenModule, AbstractBaseHarness { 9 | constructor(Integrations memory integrations) Base(integrations) {} 10 | 11 | // Linked against DummyERC20A in verification config 12 | IERC20 underlying_asset; 13 | 14 | function userAssets(address user) public view returns (uint256) { // harnessed 15 | // The assets in the underlying asset contract (not in the vault) 16 | return IERC20(asset()).balanceOf(user); 17 | } 18 | 19 | function updateVault() internal override returns (VaultCache memory vaultCache) { 20 | // initVaultCache is difficult to summarize because we can't 21 | // reason about the pass-by-value VaultCache at the start and 22 | // end of the call as separate values. So this harness 23 | // gives us a way to keep the loadVault summary when updateVault 24 | // is called 25 | vaultCache = loadVault(); 26 | if(block.timestamp - vaultCache.lastInterestAccumulatorUpdate > 0) { 27 | vaultStorage.lastInterestAccumulatorUpdate = vaultCache.lastInterestAccumulatorUpdate; 28 | vaultStorage.accumulatedFees = vaultCache.accumulatedFees; 29 | 30 | vaultStorage.totalShares = vaultCache.totalShares; 31 | vaultStorage.totalBorrows = vaultCache.totalBorrows; 32 | 33 | vaultStorage.interestAccumulator = vaultCache.interestAccumulator; 34 | } 35 | return vaultCache; 36 | } 37 | 38 | function toSharesExt(uint256 amount) external view returns (uint256) { 39 | require(amount < MAX_SANE_AMOUNT, "Assets are really uint112"); 40 | VaultCache memory vaultCache = loadVault(); 41 | return Assets.wrap(uint112(amount)).toSharesDownUint(vaultCache); 42 | } 43 | 44 | function cache_cash() public view returns (Assets) { 45 | return loadVault().cash; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /certora/harness/EVCHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "../../lib/ethereum-vault-connector/src/EthereumVaultConnector.sol"; 6 | contract EVCHarness is EthereumVaultConnector { 7 | using ExecutionContext for EC; 8 | using Set for SetStorage; 9 | 10 | // Trigger the (deferred) status checks in restoreExecutionContext 11 | // explicitly. 12 | function checkStatusAllExt() external { 13 | checkStatusAll(SetType.Account); 14 | } 15 | } -------------------------------------------------------------------------------- /certora/harness/EVaultHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {Base} from "../../src/EVault/shared/Base.sol"; 6 | 7 | import {TokenModule} from "../../src/EVault/modules/Token.sol"; 8 | import {VaultModule} from "../../src/EVault/modules/Vault.sol"; 9 | import {BorrowingModule} from "../../src/EVault/modules/Borrowing.sol"; 10 | import {LiquidationModule} from "../../src/EVault/modules/Liquidation.sol"; 11 | import {InitializeModule} from "../../src/EVault/modules/Initialize.sol"; 12 | import {BalanceForwarderModule} from "../../src/EVault/modules/BalanceForwarder.sol"; 13 | import {GovernanceModule} from "../../src/EVault/modules/Governance.sol"; 14 | import {RiskManagerModule} from "../../src/EVault/modules/RiskManager.sol"; 15 | 16 | import "../../certora/harness/AbstractBaseHarness.sol"; 17 | 18 | contract EVaultHarness is 19 | Base, 20 | InitializeModule, 21 | TokenModule, 22 | VaultModule, 23 | BorrowingModule, 24 | LiquidationModule, 25 | RiskManagerModule, 26 | BalanceForwarderModule, 27 | GovernanceModule, 28 | AbstractBaseHarness 29 | { 30 | 31 | constructor( 32 | Integrations memory integrations 33 | ) Base(integrations) {} 34 | 35 | // Unlike EVault.sol, does not override methods with the useView pattern 36 | } -------------------------------------------------------------------------------- /certora/harness/ModuleDispatchHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "../../src/EVault/modules/ModuleDispatch.sol"; 7 | 8 | contract ModuleDispatchHarness is ModuleDispatch { 9 | constructor(Integrations memory integrations) Base(integrations) {} 10 | } 11 | -------------------------------------------------------------------------------- /certora/harness/TokenHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "../../src/EVault/modules/Token.sol"; 5 | import "../../src/EVault/shared/types/Types.sol"; 6 | 7 | contract TokenHarness is TokenModule { 8 | // for amount.toShares() 9 | using TypesLib for uint256; 10 | 11 | constructor(Integrations memory integrations) Base(integrations) {} 12 | 13 | function transferFromInternalHarnessed(address from, address to, uint256 amount) public returns (bool) { 14 | // This is similar to the body of Token.transferFromInternal 15 | // when it gets its arguments from Token.transfer. 16 | // It is not harnessed directly since Token.transferFromInternal is private 17 | // and we want to avoid munging. 18 | // This is used for the enforceCollateralTransfer function 19 | Shares shares = amount.toShares(); 20 | if (from == to) revert E_SelfTransfer(); 21 | decreaseAllowance(from, from, shares); 22 | transferBalance(from, to, shares); 23 | return true; 24 | } 25 | } -------------------------------------------------------------------------------- /certora/harness/healthStatus/BalanceForwarderHSHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import "../../../src/interfaces/IPriceOracle.sol"; 4 | import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 5 | import "../../../certora/harness/AbstractBaseHarness.sol"; 6 | import "../../../src/EVault/modules/RiskManager.sol"; 7 | import "../../../src/EVault/modules/BalanceForwarder.sol"; 8 | 9 | // To prove the Health Status rule we need to include the RiskManager module 10 | // which implemeants the status check 11 | contract BalanceForwarderHSHarness is BalanceForwarderModule, RiskManagerModule, 12 | AbstractBaseHarness { 13 | constructor(Integrations memory integrations) Base(integrations) {} 14 | } -------------------------------------------------------------------------------- /certora/harness/healthStatus/BorrowingHSHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import "../../../src/interfaces/IPriceOracle.sol"; 4 | import "../../../certora/harness/AbstractBaseHarness.sol"; 5 | import "../../../src/EVault/modules/RiskManager.sol"; 6 | import "../../../src/EVault/modules/Borrowing.sol"; 7 | 8 | // To prove the Health Status rule we need to include the RiskManager module 9 | // which implemeants the status check 10 | contract BorrowingHSHarness is BorrowingModule, RiskManagerModule, 11 | AbstractBaseHarness { 12 | constructor(Integrations memory integrations) Base(integrations) {} 13 | } -------------------------------------------------------------------------------- /certora/harness/healthStatus/GovernanceHSHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import "../../../src/interfaces/IPriceOracle.sol"; 4 | import "../../../certora/harness/AbstractBaseHarness.sol"; 5 | import "../../../src/EVault/modules/RiskManager.sol"; 6 | import "../../../src/EVault/modules/Governance.sol"; 7 | 8 | // To prove the Health Status rule we need to include the RiskManager module 9 | // which implemeants the status check 10 | contract GovernanceHSHarness is GovernanceModule, RiskManagerModule, 11 | AbstractBaseHarness { 12 | constructor(Integrations memory integrations) Base(integrations) {} 13 | } -------------------------------------------------------------------------------- /certora/harness/healthStatus/InitializeHSHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import "../../../src/interfaces/IPriceOracle.sol"; 4 | import "../../../certora/harness/AbstractBaseHarness.sol"; 5 | import "../../../src/EVault/modules/RiskManager.sol"; 6 | import "../../../src/EVault/modules/Initialize.sol"; 7 | 8 | // To prove the Health Status rule we need to include the RiskManager module 9 | // which implemeants the status check 10 | contract InitializeHSHarness is InitializeModule, RiskManagerModule, 11 | AbstractBaseHarness { 12 | constructor(Integrations memory integrations) Base(integrations) {} 13 | } -------------------------------------------------------------------------------- /certora/harness/healthStatus/LiquidationHSHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import "../../../src/interfaces/IPriceOracle.sol"; 4 | import "../../../certora/harness/AbstractBaseHarness.sol"; 5 | import "../../../src/EVault/modules/RiskManager.sol"; 6 | import "../../../src/EVault/modules/Liquidation.sol"; 7 | 8 | // To prove the Health Status rule we need to include the RiskManager module 9 | // which implemeants the status check 10 | contract LiquidationHSHarness is LiquidationModule, RiskManagerModule, 11 | AbstractBaseHarness { 12 | constructor(Integrations memory integrations) Base(integrations) {} 13 | 14 | function hasDebtSocialization() external returns (bool) { 15 | VaultCache memory vaultCache = loadVault(); 16 | return vaultCache.configFlags.isNotSet(CFG_DONT_SOCIALIZE_DEBT); 17 | } 18 | } -------------------------------------------------------------------------------- /certora/harness/healthStatus/TokenHSHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import "../../../src/interfaces/IPriceOracle.sol"; 4 | import "../../../certora/harness/AbstractBaseHarness.sol"; 5 | import "../../../src/EVault/modules/RiskManager.sol"; 6 | import "../../../src/EVault/modules/Token.sol"; 7 | 8 | // To prove the Health Status rule we need to include the RiskManager module 9 | // which implemeants the status check 10 | contract TokenHSHarness is TokenModule, RiskManagerModule, 11 | AbstractBaseHarness { 12 | constructor(Integrations memory integrations) Base(integrations) {} 13 | } -------------------------------------------------------------------------------- /certora/harness/healthStatus/VaultHSHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import "../../../src/interfaces/IPriceOracle.sol"; 4 | import "../../../certora/harness/AbstractBaseHarness.sol"; 5 | import "../../../src/EVault/modules/RiskManager.sol"; 6 | import "../../../src/EVault/modules/Vault.sol"; 7 | 8 | // To prove the Health Status rule we need to include the RiskManager module 9 | // which implemeants the status check 10 | contract VaultHSHarness is VaultModule, RiskManagerModule, 11 | AbstractBaseHarness { 12 | constructor(Integrations memory integrations) Base(integrations) {} 13 | } -------------------------------------------------------------------------------- /certora/harness/modules/BalanceForwarderHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import "../../../src/interfaces/IPriceOracle.sol"; 4 | import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 5 | import "../../../certora/harness/AbstractBaseHarness.sol"; 6 | import "../../../src/EVault/modules/BalanceForwarder.sol"; 7 | 8 | contract BalanceForwarderHarness is BalanceForwarder, AbstractBaseHarness { 9 | constructor(Integrations memory integrations) BalanceForwarder(integrations) {} 10 | } -------------------------------------------------------------------------------- /certora/harness/modules/BorrowingHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 4 | import "../../../certora/harness/AbstractBaseHarness.sol"; 5 | import "../../../src/EVault/modules/Borrowing.sol"; 6 | 7 | contract BorrowingHarness is Borrowing, AbstractBaseHarness { 8 | constructor(Integrations memory integrations) Borrowing (integrations) {} 9 | } -------------------------------------------------------------------------------- /certora/harness/modules/GovernanceHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 4 | import "../../../certora/harness/AbstractBaseHarness.sol"; 5 | import "../../../src/EVault/modules/RiskManager.sol"; 6 | import "../../../src/EVault/modules/Governance.sol"; 7 | 8 | contract GovernanceHarness is Governance, AbstractBaseHarness, RiskManagerModule{ 9 | constructor(Integrations memory integrations) Governance (integrations) {} 10 | 11 | function getAccountBalance(address account) external view returns (Shares balance){ 12 | UserStorage storage user = vaultStorage.users[account]; 13 | (balance, ) = user.getBalanceAndBalanceForwarder(); 14 | } 15 | 16 | function getGovernorReceiver() external view returns (address governorReceiver){ 17 | governorReceiver = vaultStorage.feeReceiver; 18 | } 19 | 20 | function getProtocolFeeConfig(address vault) external view returns (address protocolReceiver, uint16 protocolFee){ 21 | (protocolReceiver, protocolFee) = protocolConfig.protocolFeeConfig(address(this)); 22 | } 23 | 24 | function getTotalShares() external view returns (Shares){ 25 | return vaultStorage.totalShares; 26 | } 27 | 28 | function getAccumulatedFees() external view returns (Shares){ 29 | VaultCache memory vaultCache; 30 | initVaultCache(vaultCache); 31 | return vaultCache.accumulatedFees; 32 | } 33 | 34 | function getLastAccumulated() external view returns (uint256){ 35 | return uint256(vaultStorage.lastInterestAccumulatorUpdate); 36 | } 37 | 38 | function getLTVHarness(address collateral, bool liquidation) public view virtual returns (ConfigAmount) { 39 | return getLTV(collateral, liquidation); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /certora/harness/modules/InitializeHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 4 | import "../../../certora/harness/AbstractBaseHarness.sol"; 5 | import "../../../src/EVault/modules/Initialize.sol"; 6 | 7 | contract InitializeHarness is Initialize, AbstractBaseHarness { 8 | constructor(Integrations memory integrations) Initialize(integrations) {} 9 | } -------------------------------------------------------------------------------- /certora/harness/modules/LiquidationHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | import "../../../src/interfaces/IPriceOracle.sol"; 5 | import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 6 | import "../../../certora/harness/AbstractBaseHarness.sol"; 7 | import "../../../src/EVault/modules/Liquidation.sol"; 8 | 9 | contract LiquidationHarness is AbstractBaseHarness, Liquidation { 10 | 11 | constructor(Integrations memory integrations) Liquidation(integrations) {} 12 | 13 | function calculateLiquidityExternal( 14 | address account 15 | ) public view returns (uint256 collateralValue, uint256 liabilityValue) { 16 | return calculateLiquidity(loadVault(), account, getCollaterals(account), true); 17 | } 18 | 19 | function calculateLiquidityLiquidation( 20 | address account 21 | ) public view returns (uint256 collateralValue, uint256 liabilityValue) { 22 | return calculateLiquidity(loadVault(), account, getCollaterals(account), false); 23 | } 24 | 25 | 26 | function calculateLiquidationExt( 27 | VaultCache memory vaultCache, 28 | address liquidator, 29 | address violator, 30 | address collateral, 31 | uint256 desiredRepay 32 | ) external view returns (LiquidationCache memory liqCache) { 33 | return calculateLiquidation(vaultCache, liquidator, violator, collateral, desiredRepay); 34 | } 35 | 36 | function isRecognizedCollateralExt(address collateral) external view virtual returns (bool) { 37 | return isRecognizedCollateral(collateral); 38 | } 39 | 40 | function getLiquidator() external returns (address liquidator) { 41 | (, liquidator) = initOperation(OP_LIQUIDATE, CHECKACCOUNT_CALLER); 42 | } 43 | 44 | function getCurrentOwedExt(VaultCache memory vaultCache, address violator) external view returns (Assets) { 45 | return getCurrentOwed(vaultCache, violator).toAssetsUp(); 46 | } 47 | 48 | function getCollateralValueExt(VaultCache memory vaultCache, address account, address collateral, bool liquidation) 49 | external 50 | view 51 | returns (uint256 value) { 52 | return getCollateralValue(vaultCache, account, collateral, liquidation); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /certora/harness/modules/RiskManagerHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import "../../../src/interfaces/IPriceOracle.sol"; 4 | import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 5 | import "../../../certora/harness/AbstractBaseHarness.sol"; 6 | import "../../../src/EVault/modules/RiskManager.sol"; 7 | 8 | contract RiskManagerHarness is RiskManager, AbstractBaseHarness { 9 | constructor(Integrations memory integrations) RiskManager(integrations) {} 10 | } -------------------------------------------------------------------------------- /certora/harness/modules/TokenHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 4 | import "../../../certora/harness/AbstractBaseHarness.sol"; 5 | import "../../../src/EVault/modules/Token.sol"; 6 | 7 | contract TokenHarness is Token, AbstractBaseHarness { 8 | constructor(Integrations memory integrations) Token(integrations) {} 9 | } -------------------------------------------------------------------------------- /certora/harness/modules/VaultHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import {ERC20} from "../../../lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 4 | import "../../../certora/harness/AbstractBaseHarness.sol"; 5 | import "../../../src/EVault/modules/Vault.sol"; 6 | 7 | contract VaultHarness is Vault, AbstractBaseHarness { 8 | constructor(Integrations memory integrations) Vault(integrations) {} 9 | } -------------------------------------------------------------------------------- /certora/helpers/DummyERC20A.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity ^0.8.0; 3 | import "./DummyERC20Impl.sol"; 4 | 5 | contract DummyERC20A is DummyERC20Impl {} -------------------------------------------------------------------------------- /certora/helpers/DummyERC20B.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity ^0.8.0; 3 | import "./DummyERC20Impl.sol"; 4 | 5 | contract DummyERC20B is DummyERC20Impl {} -------------------------------------------------------------------------------- /certora/helpers/DummyERC20Impl.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | // with mint 5 | contract DummyERC20Impl { 6 | uint256 t; 7 | mapping (address => uint256) b; 8 | mapping (address => mapping (address => uint256)) a; 9 | 10 | // string private name; // change public to private 11 | // string private symbol; // change public to private 12 | uint public decimals; 13 | 14 | function name() public returns (string memory) { // added for testing 15 | return ""; 16 | } 17 | 18 | function symbol() public returns (string memory) { // added for testing 19 | return ""; 20 | } 21 | 22 | function myAddress() public returns (address) { 23 | return address(this); 24 | } 25 | 26 | function add(uint a, uint b) internal pure returns (uint256) { 27 | uint c = a +b; 28 | require (c >= a); 29 | return c; 30 | } 31 | function sub(uint a, uint b) internal pure returns (uint256) { 32 | require (a>=b); 33 | return a-b; 34 | } 35 | 36 | function totalSupply() external view returns (uint256) { 37 | return t; 38 | } 39 | function balanceOf(address account) external view returns (uint256) { 40 | return b[account]; 41 | } 42 | function transfer(address recipient, uint256 amount) external returns (bool) { 43 | b[msg.sender] = sub(b[msg.sender], amount); 44 | b[recipient] = add(b[recipient], amount); 45 | return true; 46 | } 47 | function allowance(address owner, address spender) external view returns (uint256) { 48 | return a[owner][spender]; 49 | } 50 | function approve(address spender, uint256 amount) external returns (bool) { 51 | a[msg.sender][spender] = amount; 52 | return true; 53 | } 54 | 55 | function transferFrom( 56 | address sender, 57 | address recipient, 58 | uint256 amount 59 | ) external returns (bool) { 60 | b[sender] = sub(b[sender], amount); 61 | b[recipient] = add(b[recipient], amount); 62 | a[sender][msg.sender] = sub(a[sender][msg.sender], amount); 63 | return true; 64 | } 65 | } -------------------------------------------------------------------------------- /certora/helpers/DummyETokenA.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "certora/harness/TokenHarness.sol"; 5 | 6 | contract DummyETokenA is TokenHarness { 7 | constructor(Integrations memory integrations) TokenHarness(integrations) {} 8 | } -------------------------------------------------------------------------------- /certora/helpers/DummyETokenB.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "certora/harness/TokenHarness.sol"; 5 | 6 | contract DummyETokenB is TokenHarness { 7 | constructor(Integrations memory integrations) TokenHarness(integrations) {} 8 | } -------------------------------------------------------------------------------- /certora/scripts/runERC4626RulesSeparatelyBaseConf.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import subprocess 3 | 4 | parser = argparse.ArgumentParser() 5 | parser.add_argument('-M', '--batchMsg', metavar='M', type=str, nargs='?', 6 | default='', 7 | help='a message for all the jobs') 8 | 9 | # The commented out ones are the ones that need a special config file. 10 | # Those can be run easily by running runERC4626RulesSplitConfs.py 11 | rule_names = [ 12 | # "assetsMoreThanSupply", 13 | "contributingProducesShares", 14 | "conversionOfZero", 15 | "conversionWeakIntegrity", 16 | "conversionWeakMonotonicity", 17 | # "convertToAssetsWeakAdditivity", 18 | # "convertToSharesWeakAdditivity", 19 | # "depositMonotonicity", 20 | # "dustFavorsTheHouse", 21 | # "dustFavorsTheHouseAssets", 22 | # "noAssetsIfNoSupply", 23 | # "noSupplyIfNoAssets", 24 | # "onlyContributionMethodsReduceAssets", 25 | "reclaimingProducesAssets", 26 | "redeemingAllValidity", 27 | "totalSupplyIsSumOfBalances", 28 | # "totalsMonotonicity", 29 | "zeroDepositZeroShares", 30 | "underlyingCannotChange", 31 | # "vaultSolvency", 32 | ] 33 | 34 | for name in rule_names: 35 | args = parser.parse_args() 36 | script = "certora/conf/ERC4626Split/VaultERC4626.conf" 37 | command = f"certoraRun {script} --rule \"{name}\" --msg \"{name} : {args.batchMsg}\"" 38 | print(f"runing {command}") 39 | subprocess.run(command, shell=True) -------------------------------------------------------------------------------- /certora/scripts/runERC4626RulesSplitConfs.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import subprocess 3 | 4 | parser = argparse.ArgumentParser() 5 | parser.add_argument('-M', '--batchMsg', metavar='M', type=str, nargs='?', 6 | default='', 7 | help='a message for all the jobs') 8 | 9 | erc4626_confs = { 10 | "", 11 | "-assetsMoreThanSupply", 12 | "-convertToAssetsWeakAdditivity", 13 | "-convertToSharesWeakAdditivity", 14 | "-depositMonotonicity", 15 | "-dustFavorsTheHouse", 16 | "-dustFavorsTheHouseAssets", 17 | "-noAssetsIfNoSupply", 18 | "-noSupplyIfNoAssets", 19 | "-onlyContributionMethodsReduce", 20 | "-totalsMonotonicity", 21 | "-vaultSolvency-most", 22 | "-vaultSolvency-redeem", 23 | "-vaultSolvency-withdraw", 24 | # In case the invariant times out for withdraw 25 | "-vaultSolvency-withdraw-as-rule" 26 | } 27 | 28 | for name in erc4626_confs: 29 | args = parser.parse_args() 30 | script = f"certora/conf/ERC4626Split/VaultERC4626{name}.conf" 31 | command = f"certoraRun {script} --msg \"{name} : {args.batchMsg}\"" 32 | print(f"runing {command}") 33 | subprocess.run(command, shell=True) 34 | 35 | -------------------------------------------------------------------------------- /certora/specs/BalanceForwarder.spec: -------------------------------------------------------------------------------- 1 | import "Base.spec"; 2 | 3 | methods { 4 | function _.balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external => NONDET; 5 | } 6 | 7 | //passing: 8 | // https://prover.certora.com/output/65266/ee7d7eb1364e4589a271b46267aa4742?anonymousKey=82512541ee02ddf9a6830a9555878361946ac19a 9 | rule enableBalanceForwarder { 10 | address account; 11 | env e1; 12 | env e2; 13 | require actualCaller(e1) == account; 14 | enableBalanceForwarder(e1); 15 | assert balanceForwarderEnabled(e2, account); 16 | } 17 | 18 | // passing: 19 | // https://prover.certora.com/output/65266/ee7d7eb1364e4589a271b46267aa4742?anonymousKey=82512541ee02ddf9a6830a9555878361946ac19a 20 | rule disableBalanceForwarder { 21 | address account; 22 | env e1; 23 | env e2; 24 | require actualCaller(e1) == account; 25 | disableBalanceForwarder(e1); 26 | assert !balanceForwarderEnabled(e2, account); 27 | } -------------------------------------------------------------------------------- /certora/specs/Cache.spec: -------------------------------------------------------------------------------- 1 | // passing 2 | // run: https://prover.certora.com/output/65266/7c027fe6b03f4ead8d1fc08b876c8e75?anonymousKey=475104b4504c765772a29b9124ee15355a4cf2c9 3 | rule updateVault_no_unexpected_reverts { 4 | env e; 5 | 6 | // revert case run: 7 | // https://prover.certora.com/output/65266/8d688972399441b6baaca896f085402a?anonymousKey=0e79c1406bd82a2ad2672404bb8b62d184cd537b 8 | require e.msg.value == 0; 9 | uint256 lastInterestAccUpd = getlastInterestAccumulatorUpdate(e); 10 | 11 | // assignment to deltaT 12 | require lastInterestAccUpd <= e.block.timestamp; 13 | 14 | // newTotalBorrows assigment, prevent divide by zero 15 | require getInterestAcc(e) > 0; 16 | 17 | // typecast of newAccumulatedFees 18 | require getAccumulatedFees(e) < getTotalShares(e); 19 | 20 | updateVaultExt@withrevert(e); 21 | assert !lastReverted; 22 | } -------------------------------------------------------------------------------- /certora/specs/LoadVaultSummary.spec: -------------------------------------------------------------------------------- 1 | import "./Base.spec"; 2 | methods { 3 | function Cache.loadVault() internal returns (BaseHarness.VaultCache memory) with (env e) => CVLLoadVaultAssumeNoUpdate(e); 4 | 5 | function storage_lastInterestAccumulatorUpdate() external returns (uint48) envfree; 6 | function storage_cash() external returns (BaseHarness.Assets) envfree; 7 | function storage_supplyCap() external returns (uint256) envfree; 8 | function storage_borrowCap() external returns (uint256) envfree; 9 | function storage_hookedOps() external returns (BaseHarness.Flags) envfree; 10 | function storage_snapshotInitialized() external returns (bool) envfree; 11 | function storage_totalShares() external returns (BaseHarness.Shares) envfree; 12 | function storage_totalBorrows() external returns (BaseHarness.Owed) envfree; 13 | function storage_accumulatedFees() external returns (BaseHarness.Shares) envfree; 14 | function storage_interestAccumulator() external returns (uint256) envfree; 15 | function storage_configFlags() external returns (BaseHarness.Flags) envfree; 16 | } 17 | 18 | 19 | 20 | // need to make sure successive calls only return different values 21 | // when this is actually possible in the real call... 22 | // * calls with the same env will return all the same values 23 | // the passage of time is not actually relevant to the spec because 24 | // in all the rules, only one env is ever created per rule. 25 | // The parts of the cache about interest are not relevant to the specs 26 | 27 | function CVLLoadVaultAssumeNoUpdate(env e) returns BaseHarness.VaultCache { 28 | BaseHarness.VaultCache vaultCache; 29 | uint48 lastUpdate = storage_lastInterestAccumulatorUpdate(); 30 | BaseHarness.Owed oldTotalBorrows = storage_totalBorrows(); 31 | BaseHarness.Shares oldTotalShares = storage_totalShares(); 32 | require vaultCache.cash == storage_cash(); 33 | uint48 timestamp48 = require_uint48(e.block.timestamp); 34 | bool updated = timestamp48 != lastUpdate; 35 | require !updated; 36 | require vaultCache.lastInterestAccumulatorUpdate == lastUpdate; 37 | require vaultCache.totalBorrows == oldTotalBorrows; 38 | require vaultCache.totalShares == oldTotalShares; 39 | require vaultCache.accumulatedFees == storage_accumulatedFees(); 40 | require vaultCache.interestAccumulator == storage_interestAccumulator(); 41 | 42 | // unmodified values 43 | require vaultCache.supplyCap == storage_supplyCap(); 44 | require vaultCache.borrowCap == storage_borrowCap(); 45 | require vaultCache.hookedOps == storage_hookedOps(); 46 | require vaultCache.configFlags == storage_configFlags(); 47 | require vaultCache.snapshotInitialized == storage_snapshotInitialized(); 48 | 49 | require vaultCache.asset == erc20; 50 | require vaultCache.oracle == oracleAddress; 51 | require vaultCache.unitOfAccount == unitOfAccount; 52 | require oracleAddress != 0; 53 | require unitOfAccount != 0; 54 | 55 | return vaultCache; 56 | } -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | optimizer = true 7 | optimizer_runs = 20_000 8 | 9 | fs_permissions = [{ access = "read-write", path = "./"}] 10 | 11 | gas_reports = ["*"] 12 | 13 | 14 | [profile.default.fmt] 15 | line_length = 120 16 | tab_width = 4 17 | bracket_spacing = false 18 | int_types = "long" 19 | quote_style = "double" 20 | number_underscore = "preserve" 21 | override_spacing = true 22 | wrap_comments = true 23 | ignore = [ 24 | "src/EVault/EVault.sol", 25 | "src/EVault/shared/types/VaultStorage.sol", 26 | "src/EVault/shared/types/VaultCache.sol", 27 | "test/mocks/TestERC20.sol" 28 | ] 29 | 30 | 31 | [profile.default.fuzz] 32 | max_test_rejects = 1_000_000 33 | seed = "0xee1d0f7d9556539a9c0e26aed5e63556" 34 | runs = 1000 35 | 36 | [profile.default.invariant] 37 | call_override = false 38 | depth = 50 39 | runs = 1000 40 | -------------------------------------------------------------------------------- /medusa.json: -------------------------------------------------------------------------------- 1 | { 2 | "fuzzing": { 3 | "workers": 12, 4 | "workerResetLimit": 50, 5 | "timeout": 0, 6 | "testLimit": 0, 7 | "callSequenceLength": 100, 8 | "corpusDirectory": "test/invariants/_corpus/medusa", 9 | "coverageEnabled": true, 10 | "deploymentOrder": [ 11 | "Tester" 12 | ], 13 | "targetContracts": [ 14 | "Tester" 15 | ], 16 | "targetContractsBalances": ["0xffffffffffffffffffffffffffffff"], 17 | "constructorArgs": {}, 18 | "deployerAddress": "0x30000", 19 | "senderAddresses": [ 20 | "0x10000", 21 | "0x20000", 22 | "0x30000" 23 | ], 24 | "blockNumberDelayMax": 60480, 25 | "blockTimestampDelayMax": 604800, 26 | "blockGasLimit": 12500000000, 27 | "transactionGasLimit": 1250000000, 28 | "testing": { 29 | "stopOnFailedTest": true, 30 | "stopOnFailedContractMatching": false, 31 | "stopOnNoTests": true, 32 | "testAllContracts": false, 33 | "traceAll": false, 34 | "assertionTesting": { 35 | "enabled": true, 36 | "testViewMethods": true, 37 | "panicCodeConfig": { 38 | "failOnCompilerInsertedPanic": false, 39 | "failOnAssertion": true, 40 | "failOnArithmeticUnderflow": false, 41 | "failOnDivideByZero": false, 42 | "failOnEnumTypeConversionOutOfBounds": false, 43 | "failOnIncorrectStorageAccess": false, 44 | "failOnPopEmptyArray": false, 45 | "failOnOutOfBoundsArrayAccess": false, 46 | "failOnAllocateTooMuchMemory": false, 47 | "failOnCallUninitializedVariable": false 48 | } 49 | }, 50 | "propertyTesting": { 51 | "enabled": true, 52 | "testPrefixes": [ 53 | "fuzz_", 54 | "echidna_" 55 | ] 56 | }, 57 | "optimizationTesting": { 58 | "enabled": false, 59 | "testPrefixes": [ 60 | "optimize_" 61 | ] 62 | } 63 | }, 64 | "chainConfig": { 65 | "codeSizeCheckDisabled": true, 66 | "cheatCodes": { 67 | "cheatCodesEnabled": true, 68 | "enableFFI": false 69 | } 70 | } 71 | }, 72 | "compilation": { 73 | "platform": "crytic-compile", 74 | "platformConfig": { 75 | "target": "test/invariants/Tester.t.sol", 76 | "solcVersion": "", 77 | "exportDirectory": "", 78 | "args": [ 79 | "--solc-remaps", 80 | "@crytic/properties/=lib/properties/ forge-std/=lib/forge-std/src/ ds-test/=lib/forge-std/lib/ds-test/src/ evc/=lib/ethereum-vault-connector/src/ solmate/=lib/solmate/src/ openzeppelin/=lib/openzeppelin-contracts/contracts/", 81 | "--compile-libraries=(Pretty,0xf01),(Strings,0xf02)" 82 | ] 83 | } 84 | }, 85 | "logging": { 86 | "level": "info", 87 | "logDirectory": "" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | ethereum-vault-connector/=lib/ethereum-vault-connector/src/ 2 | permit2/=lib/permit2/ 3 | ds-test/=lib/ethereum-vault-connector/lib/forge-std/lib/ds-test/src/ 4 | erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ 5 | forge-gas-snapshot/=lib/permit2/lib/forge-gas-snapshot/src/ 6 | forge-std/=lib/forge-std/src/ 7 | openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/ 8 | solmate/=lib/permit2/lib/solmate/ 9 | -------------------------------------------------------------------------------- /slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "filter_paths": "(/test/|/lib/|/scripts/)" 3 | } -------------------------------------------------------------------------------- /src/EVault/modules/BalanceForwarder.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {IBalanceForwarder} from "../IEVault.sol"; 6 | import {Base} from "../shared/Base.sol"; 7 | 8 | import "../shared/types/Types.sol"; 9 | 10 | /// @title BalanceForwarderModule 11 | /// @custom:security-contact security@euler.xyz 12 | /// @author Euler Labs (https://www.eulerlabs.com/) 13 | /// @notice An EVault module handling communication with a balance tracker contract. 14 | abstract contract BalanceForwarderModule is IBalanceForwarder, Base { 15 | /// @inheritdoc IBalanceForwarder 16 | function balanceTrackerAddress() public view virtual reentrantOK returns (address) { 17 | return address(balanceTracker); 18 | } 19 | 20 | /// @inheritdoc IBalanceForwarder 21 | function balanceForwarderEnabled(address account) public view virtual nonReentrantView returns (bool) { 22 | return vaultStorage.users[account].isBalanceForwarderEnabled(); 23 | } 24 | 25 | /// @inheritdoc IBalanceForwarder 26 | function enableBalanceForwarder() public virtual nonReentrant { 27 | if (address(balanceTracker) == address(0)) revert E_NotSupported(); 28 | 29 | address account = EVCAuthenticate(); 30 | UserStorage storage user = vaultStorage.users[account]; 31 | 32 | bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled(); 33 | 34 | user.setBalanceForwarder(true); 35 | balanceTracker.balanceTrackerHook(account, user.getBalance().toUint(), false); 36 | 37 | if (!wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, true); 38 | } 39 | 40 | /// @inheritdoc IBalanceForwarder 41 | function disableBalanceForwarder() public virtual nonReentrant { 42 | if (address(balanceTracker) == address(0)) revert E_NotSupported(); 43 | 44 | address account = EVCAuthenticate(); 45 | UserStorage storage user = vaultStorage.users[account]; 46 | 47 | bool wasBalanceForwarderEnabled = user.isBalanceForwarderEnabled(); 48 | 49 | user.setBalanceForwarder(false); 50 | balanceTracker.balanceTrackerHook(account, 0, false); 51 | 52 | if (wasBalanceForwarderEnabled) emit BalanceForwarderStatus(account, false); 53 | } 54 | } 55 | 56 | /// @dev Deployable module contract 57 | contract BalanceForwarder is BalanceForwarderModule { 58 | constructor(Integrations memory integrations) Base(integrations) {} 59 | } 60 | -------------------------------------------------------------------------------- /src/EVault/shared/AssetTransfers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {SafeERC20Lib} from "./lib/SafeERC20Lib.sol"; 6 | import {Base} from "./Base.sol"; 7 | 8 | import "./types/Types.sol"; 9 | 10 | /// @title AssetTransfers 11 | /// @custom:security-contact security@euler.xyz 12 | /// @author Euler Labs (https://www.eulerlabs.com/) 13 | /// @notice Transfer assets into and out of the vault 14 | abstract contract AssetTransfers is Base { 15 | using TypesLib for uint256; 16 | using SafeERC20Lib for IERC20; 17 | 18 | function pullAssets(VaultCache memory vaultCache, address from, Assets amount) internal virtual { 19 | vaultCache.asset.safeTransferFrom(from, address(this), amount.toUint(), permit2); 20 | vaultStorage.cash = vaultCache.cash = vaultCache.cash + amount; 21 | } 22 | 23 | /// @dev If the `CFG_EVC_COMPATIBLE_ASSET` flag is not set (default), the function will protect users from 24 | /// mistakenly sending funds to the EVC sub-accounts. Functions that push tokens out (`withdraw`, `redeem`, 25 | /// `borrow`) accept a `receiver` argument. If the user sets one of their sub-accounts (not the owner) as the 26 | /// receiver, funds would be lost because a regular asset doesn't support the EVC's sub-accounts. The private key to 27 | /// a sub-account (not the owner) is not known, so the user would not be able to move the funds out. The function 28 | /// will make a best effort to prevent this by checking if the receiver of the token is recognized by EVC as a 29 | /// non-owner sub-account. In other words, if there is an account registered in EVC as the owner for the intended 30 | /// receiver, the transfer will be prevented. However, there is no guarantee that EVC will have the owner 31 | /// registered. If the asset itself is compatible with EVC, it is safe to set the flag and send the asset to a 32 | /// non-owner sub-account. 33 | function pushAssets(VaultCache memory vaultCache, address to, Assets amount) internal virtual { 34 | if ( 35 | to == address(0) 36 | || (vaultCache.configFlags.isNotSet(CFG_EVC_COMPATIBLE_ASSET) && isKnownNonOwnerAccount(to)) 37 | ) { 38 | revert E_BadAssetReceiver(); 39 | } 40 | 41 | vaultStorage.cash = vaultCache.cash = vaultCache.cash - amount; 42 | vaultCache.asset.safeTransfer(to, amount.toUint()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/EVault/shared/Constants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | // Implementation internals 5 | 6 | // asset amounts are shifted left by this number of bits for increased precision of debt tracking. 7 | uint256 constant INTERNAL_DEBT_PRECISION_SHIFT = 31; 8 | // max amount for Assets and Shares custom types based on a uint112. 9 | uint256 constant MAX_SANE_AMOUNT = type(uint112).max; 10 | // max debt amount fits in uint144 (112 + 31 bits). 11 | // Last 31 bits are zeros to ensure max debt rounded up equals max sane amount. 12 | uint256 constant MAX_SANE_DEBT_AMOUNT = uint256(MAX_SANE_AMOUNT) << INTERNAL_DEBT_PRECISION_SHIFT; 13 | // proxy trailing calldata length in bytes. 14 | // Three addresses, 20 bytes each: vault underlying asset, oracle and unit of account + 4 empty bytes. 15 | uint256 constant PROXY_METADATA_LENGTH = 64; 16 | // gregorian calendar 17 | uint256 constant SECONDS_PER_YEAR = 365.2425 * 86400; 18 | // max interest rate accepted from IRM. 1,000,000% APY: floor(((1000000 / 100 + 1)**(1/(86400*365.2425)) - 1) * 1e27) 19 | uint256 constant MAX_ALLOWED_INTEREST_RATE = 291867278914945094175; 20 | // max valid value of the ConfigAmount custom type, signifying 100% 21 | uint16 constant CONFIG_SCALE = 1e4; 22 | 23 | // Account status checks special values 24 | 25 | // no account status checks should be scheduled 26 | address constant CHECKACCOUNT_NONE = address(0); 27 | // account status check should be scheduled for the authenticated account 28 | address constant CHECKACCOUNT_CALLER = address(1); 29 | 30 | // Operations 31 | 32 | uint32 constant OP_DEPOSIT = 1 << 0; 33 | uint32 constant OP_MINT = 1 << 1; 34 | uint32 constant OP_WITHDRAW = 1 << 2; 35 | uint32 constant OP_REDEEM = 1 << 3; 36 | uint32 constant OP_TRANSFER = 1 << 4; 37 | uint32 constant OP_SKIM = 1 << 5; 38 | uint32 constant OP_BORROW = 1 << 6; 39 | uint32 constant OP_REPAY = 1 << 7; 40 | uint32 constant OP_REPAY_WITH_SHARES = 1 << 8; 41 | uint32 constant OP_PULL_DEBT = 1 << 9; 42 | uint32 constant OP_CONVERT_FEES = 1 << 10; 43 | uint32 constant OP_LIQUIDATE = 1 << 11; 44 | uint32 constant OP_FLASHLOAN = 1 << 12; 45 | uint32 constant OP_TOUCH = 1 << 13; 46 | uint32 constant OP_VAULT_STATUS_CHECK = 1 << 14; 47 | // Delimiter of possible operations 48 | uint32 constant OP_MAX_VALUE = 1 << 15; 49 | 50 | // Config Flags 51 | 52 | // When flag is set, debt socialization during liquidation is disabled 53 | uint32 constant CFG_DONT_SOCIALIZE_DEBT = 1 << 0; 54 | // When flag is set, asset is considered to be compatible with EVC sub-accounts and protections 55 | // against sending assets to sub-accounts are disabled 56 | uint32 constant CFG_EVC_COMPATIBLE_ASSET = 1 << 1; 57 | // Delimiter of possible config flags 58 | uint32 constant CFG_MAX_VALUE = 1 << 2; 59 | 60 | // EVC authentication 61 | 62 | // in order to perform these operations, the account doesn't need to have the vault installed as a controller 63 | uint32 constant CONTROLLER_NEUTRAL_OPS = OP_DEPOSIT | OP_MINT | OP_WITHDRAW | OP_REDEEM | OP_TRANSFER | OP_SKIM 64 | | OP_REPAY | OP_REPAY_WITH_SHARES | OP_CONVERT_FEES | OP_FLASHLOAN | OP_TOUCH | OP_VAULT_STATUS_CHECK; 65 | -------------------------------------------------------------------------------- /src/EVault/shared/Errors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /// @title Errors 6 | /// @custom:security-contact security@euler.xyz 7 | /// @author Euler Labs (https://www.eulerlabs.com/) 8 | /// @notice Contract implementing EVault's custom errors 9 | contract Errors { 10 | error E_Initialized(); 11 | error E_ProxyMetadata(); 12 | error E_SelfTransfer(); 13 | error E_InsufficientAllowance(); 14 | error E_InsufficientCash(); 15 | error E_InsufficientAssets(); 16 | error E_InsufficientBalance(); 17 | error E_InsufficientDebt(); 18 | error E_FlashLoanNotRepaid(); 19 | error E_Reentrancy(); 20 | error E_OperationDisabled(); 21 | error E_OutstandingDebt(); 22 | error E_AmountTooLargeToEncode(); 23 | error E_DebtAmountTooLargeToEncode(); 24 | error E_RepayTooMuch(); 25 | error E_TransientState(); 26 | error E_SelfLiquidation(); 27 | error E_ControllerDisabled(); 28 | error E_CollateralDisabled(); 29 | error E_ViolatorLiquidityDeferred(); 30 | error E_LiquidationCoolOff(); 31 | error E_ExcessiveRepayAmount(); 32 | error E_MinYield(); 33 | error E_BadAddress(); 34 | error E_ZeroAssets(); 35 | error E_ZeroShares(); 36 | error E_Unauthorized(); 37 | error E_CheckUnauthorized(); 38 | error E_NotSupported(); 39 | error E_EmptyError(); 40 | error E_BadBorrowCap(); 41 | error E_BadSupplyCap(); 42 | error E_BadCollateral(); 43 | error E_AccountLiquidity(); 44 | error E_NoLiability(); 45 | error E_NotController(); 46 | error E_BadFee(); 47 | error E_SupplyCapExceeded(); 48 | error E_BorrowCapExceeded(); 49 | error E_InvalidLTVAsset(); 50 | error E_NoPriceOracle(); 51 | error E_ConfigAmountTooLargeToEncode(); 52 | error E_BadAssetReceiver(); 53 | error E_BadSharesOwner(); 54 | error E_BadSharesReceiver(); 55 | error E_BadMaxLiquidationDiscount(); 56 | error E_LTVBorrow(); 57 | error E_LTVLiquidation(); 58 | error E_NotHookTarget(); 59 | } 60 | -------------------------------------------------------------------------------- /src/EVault/shared/LTVUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {Storage} from "./Storage.sol"; 6 | import "./types/Types.sol"; 7 | 8 | /// @title LTVUtils 9 | /// @custom:security-contact security@euler.xyz 10 | /// @author Euler Labs (https://www.eulerlabs.com/) 11 | /// @notice Overridable getters for LTV configuration 12 | abstract contract LTVUtils is Storage { 13 | function getLTV(address collateral, bool liquidation) internal view virtual returns (ConfigAmount) { 14 | return vaultStorage.ltvLookup[collateral].getLTV(liquidation); 15 | } 16 | 17 | function isRecognizedCollateral(address collateral) internal view virtual returns (bool) { 18 | return vaultStorage.ltvLookup[collateral].isRecognizedCollateral(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/EVault/shared/Storage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {VaultStorage, Snapshot} from "./types/Types.sol"; 6 | 7 | /// @title Storage 8 | /// @custom:security-contact security@euler.xyz 9 | /// @author Euler Labs (https://www.eulerlabs.com/) 10 | /// @notice Contract that defines the EVault's data storage 11 | abstract contract Storage { 12 | /// @notice Flag indicating if the vault has been initialized 13 | bool internal initialized; 14 | 15 | /// @notice Snapshot of vault's cash and borrows created at the beginning of an operation or a batch of operations 16 | /// @dev The snapshot is separate from VaultStorage, because it could be implemented as transient storage 17 | Snapshot internal snapshot; 18 | 19 | /// @notice A singleton VaultStorage 20 | VaultStorage internal vaultStorage; 21 | } 22 | -------------------------------------------------------------------------------- /src/EVault/shared/lib/AddressUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../Errors.sol"; 6 | 7 | /// @title AddressUtils Library 8 | /// @custom:security-contact security@euler.xyz 9 | /// @author Euler Labs (https://www.eulerlabs.com/) 10 | /// @notice The library provides a helper function for checking if provided address is a contract (has code) 11 | library AddressUtils { 12 | function checkContract(address addr) internal view returns (address) { 13 | if (addr.code.length == 0) revert Errors.E_BadAddress(); 14 | 15 | return addr; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/EVault/shared/lib/ConversionHelpers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {VaultCache} from "../types/VaultCache.sol"; 6 | 7 | /// @title ConversionHelpers Library 8 | /// @custom:security-contact security@euler.xyz 9 | /// @author Euler Labs (https://www.eulerlabs.com/) 10 | /// @notice The library provides a helper function for conversions between shares and assets 11 | library ConversionHelpers { 12 | // virtual deposit used in conversions between shares and assets, serving as exchange rate manipulation mitigation 13 | uint256 internal constant VIRTUAL_DEPOSIT_AMOUNT = 1e6; 14 | 15 | function conversionTotals(VaultCache memory vaultCache) 16 | internal 17 | pure 18 | returns (uint256 totalAssets, uint256 totalShares) 19 | { 20 | unchecked { 21 | totalAssets = 22 | vaultCache.cash.toUint() + vaultCache.totalBorrows.toAssetsUp().toUint() + VIRTUAL_DEPOSIT_AMOUNT; 23 | totalShares = vaultCache.totalShares.toUint() + VIRTUAL_DEPOSIT_AMOUNT; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/EVault/shared/lib/ProxyUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {IERC20} from "../../IEVault.sol"; 6 | import {IPriceOracle} from "../../../interfaces/IPriceOracle.sol"; 7 | 8 | import "../Constants.sol"; 9 | 10 | /// @title ProxyUtils Library 11 | /// @custom:security-contact security@euler.xyz 12 | /// @author Euler Labs (https://www.eulerlabs.com/) 13 | /// @notice The library provides helper functions for working with proxy meta data 14 | library ProxyUtils { 15 | function metadata() internal pure returns (IERC20 asset, IPriceOracle oracle, address unitOfAccount) { 16 | assembly { 17 | asset := shr(96, calldataload(sub(calldatasize(), 60))) 18 | oracle := shr(96, calldataload(sub(calldatasize(), 40))) 19 | unitOfAccount := shr(96, calldataload(sub(calldatasize(), 20))) 20 | } 21 | } 22 | 23 | // When `useView` modifier is used, the original caller's address is attached to the call data along with the 24 | // metadata 25 | function useViewCaller() internal pure returns (address viewCaller) { 26 | assembly { 27 | viewCaller := shr(96, calldataload(sub(calldatasize(), add(PROXY_METADATA_LENGTH, 20)))) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/EVault/shared/lib/RevertBytes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../Errors.sol"; 6 | 7 | /// @title RevertBytes Library 8 | /// @custom:security-contact security@euler.xyz 9 | /// @author Euler Labs (https://www.eulerlabs.com/) 10 | /// @notice The library provides a helper function for bubbling up errors 11 | library RevertBytes { 12 | function revertBytes(bytes memory errMsg) internal pure { 13 | if (errMsg.length > 0) { 14 | assembly { 15 | revert(add(32, errMsg), mload(errMsg)) 16 | } 17 | } 18 | 19 | revert Errors.E_EmptyError(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/EVault/shared/lib/SafeERC20Lib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {IERC20} from "../../IEVault.sol"; 6 | import {RevertBytes} from "./RevertBytes.sol"; 7 | import {IPermit2} from "../../../interfaces/IPermit2.sol"; 8 | 9 | /// @title SafeERC20Lib Library 10 | /// @custom:security-contact security@euler.xyz 11 | /// @author Euler Labs (https://www.eulerlabs.com/) 12 | /// @notice The library provides helpers for ERC20 transfers, including Permit2 support 13 | library SafeERC20Lib { 14 | error E_TransferFromFailed(bytes errorPermit2, bytes errorTransferFrom); 15 | 16 | // If no code exists under the token address, the function will succeed. EVault ensures this is not the case in 17 | // `initialize`. 18 | function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) 19 | internal 20 | returns (bool, bytes memory) 21 | { 22 | (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transferFrom, (from, to, value))); 23 | 24 | return isEmptyOrTrueReturn(success, data) ? (true, bytes("")) : (false, data); 25 | } 26 | 27 | function safeTransferFrom(IERC20 token, address from, address to, uint256 value, address permit2) internal { 28 | bool success; 29 | bytes memory permit2Data; 30 | bytes memory transferData; 31 | 32 | if (permit2 != address(0) && value <= type(uint160).max) { 33 | // it's safe to down-cast value to uint160 34 | (success, permit2Data) = 35 | permit2.call(abi.encodeCall(IPermit2.transferFrom, (from, to, uint160(value), address(token)))); 36 | } 37 | 38 | if (!success) { 39 | (success, transferData) = trySafeTransferFrom(token, from, to, value); 40 | } 41 | 42 | if (!success) revert E_TransferFromFailed(permit2Data, transferData); 43 | } 44 | 45 | // If no code exists under the token address, the function will succeed. EVault ensures this is not the case in 46 | // `initialize`. 47 | function safeTransfer(IERC20 token, address to, uint256 value) internal { 48 | (bool success, bytes memory data) = address(token).call(abi.encodeCall(IERC20.transfer, (to, value))); 49 | if (!isEmptyOrTrueReturn(success, data)) RevertBytes.revertBytes(data); 50 | } 51 | 52 | function isEmptyOrTrueReturn(bool callSuccess, bytes memory data) private pure returns (bool) { 53 | return callSuccess && (data.length == 0 || (data.length >= 32 && abi.decode(data, (bool)))); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/EVault/shared/types/AmountCap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {AmountCap} from "./Types.sol"; 6 | 7 | /// @title AmountCapLib 8 | /// @custom:security-contact security@euler.xyz 9 | /// @author Euler Labs (https://www.eulerlabs.com/) 10 | /// @notice Library for `AmountCap` custom type 11 | /// @dev AmountCaps are 16-bit decimal floating point values: 12 | /// * The least significant 6 bits are the exponent 13 | /// * The most significant 10 bits are the mantissa, scaled by 100 14 | /// * The special value of 0 means limit is not set 15 | /// * This is so that uninitialized storage implies no limit 16 | /// * For an actual cap value of 0, use a zero mantissa and non-zero exponent 17 | library AmountCapLib { 18 | function resolve(AmountCap self) internal pure returns (uint256) { 19 | uint256 amountCap = AmountCap.unwrap(self); 20 | 21 | if (amountCap == 0) return type(uint256).max; 22 | 23 | unchecked { 24 | // Cannot overflow because this is less than 2**256: 25 | // 10**(2**6 - 1) * (2**10 - 1) = 1.023e+66 26 | return 10 ** (amountCap & 63) * (amountCap >> 6) / 100; 27 | } 28 | } 29 | 30 | function toRawUint16(AmountCap self) internal pure returns (uint16) { 31 | return AmountCap.unwrap(self); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/EVault/shared/types/ConfigAmount.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {ConfigAmount} from "./Types.sol"; 6 | import {Errors} from "../Errors.sol"; 7 | import "../Constants.sol"; 8 | 9 | /// @title ConfigAmountLib 10 | /// @custom:security-contact security@euler.xyz 11 | /// @author Euler Labs (https://www.eulerlabs.com/) 12 | /// @notice Library for `ConfigAmount` custom type 13 | /// @dev ConfigAmounts are fixed point values encoded in 16 bits with a 1e4 precision. 14 | /// @dev The type is used to store protocol configuration values. 15 | library ConfigAmountLib { 16 | function isZero(ConfigAmount self) internal pure returns (bool) { 17 | return self.toUint16() == 0; 18 | } 19 | 20 | function toUint16(ConfigAmount self) internal pure returns (uint16) { 21 | return ConfigAmount.unwrap(self); 22 | } 23 | } 24 | 25 | function gtConfigAmount(ConfigAmount a, ConfigAmount b) pure returns (bool) { 26 | return a.toUint16() > b.toUint16(); 27 | } 28 | 29 | function gteConfigAmount(ConfigAmount a, ConfigAmount b) pure returns (bool) { 30 | return a.toUint16() >= b.toUint16(); 31 | } 32 | 33 | function ltConfigAmount(ConfigAmount a, ConfigAmount b) pure returns (bool) { 34 | return a.toUint16() < b.toUint16(); 35 | } 36 | 37 | function lteConfigAmount(ConfigAmount a, ConfigAmount b) pure returns (bool) { 38 | return a.toUint16() <= b.toUint16(); 39 | } 40 | -------------------------------------------------------------------------------- /src/EVault/shared/types/Flags.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {Flags} from "./Types.sol"; 6 | 7 | /// @title FlagsLib 8 | /// @custom:security-contact security@euler.xyz 9 | /// @author Euler Labs (https://www.eulerlabs.com/) 10 | /// @notice Library for `Flags` custom type 11 | library FlagsLib { 12 | /// @dev Are *all* of the flags in bitMask set? 13 | function isSet(Flags self, uint32 bitMask) internal pure returns (bool) { 14 | return (Flags.unwrap(self) & bitMask) == bitMask; 15 | } 16 | 17 | /// @dev Are *none* of the flags in bitMask set? 18 | function isNotSet(Flags self, uint32 bitMask) internal pure returns (bool) { 19 | return (Flags.unwrap(self) & bitMask) == 0; 20 | } 21 | 22 | function toUint32(Flags self) internal pure returns (uint32) { 23 | return Flags.unwrap(self); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/EVault/shared/types/LTVConfig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {ConfigAmount} from "./Types.sol"; 6 | 7 | /// @title LTVConfig 8 | /// @notice This packed struct is used to store LTV configuration of a collateral 9 | struct LTVConfig { 10 | // Packed slot: 2 + 2 + 2 + 6 + 4 = 16 11 | // The value of borrow LTV for originating positions 12 | ConfigAmount borrowLTV; 13 | // The value of fully converged liquidation LTV 14 | ConfigAmount liquidationLTV; 15 | // The initial value of liquidation LTV, when the ramp began 16 | ConfigAmount initialLiquidationLTV; 17 | // The timestamp when the liquidation LTV is considered fully converged 18 | uint48 targetTimestamp; 19 | // The time it takes for the liquidation LTV to converge from the initial value to the fully converged value 20 | uint32 rampDuration; 21 | } 22 | 23 | /// @title LTVConfigLib 24 | /// @custom:security-contact security@euler.xyz 25 | /// @author Euler Labs (https://www.eulerlabs.com/) 26 | /// @notice Library for getting and setting the LTV configurations 27 | library LTVConfigLib { 28 | // Is the collateral considered safe to liquidate 29 | function isRecognizedCollateral(LTVConfig memory self) internal pure returns (bool) { 30 | return self.targetTimestamp != 0; 31 | } 32 | 33 | // Get current LTV of the collateral. When liquidation LTV is lowered, it is ramped down to the target value over a 34 | // period of time. 35 | function getLTV(LTVConfig memory self, bool liquidation) internal view returns (ConfigAmount) { 36 | if (!liquidation) { 37 | return self.borrowLTV; 38 | } 39 | 40 | if (block.timestamp >= self.targetTimestamp || self.liquidationLTV >= self.initialLiquidationLTV) { 41 | return self.liquidationLTV; 42 | } 43 | 44 | uint256 currentLiquidationLTV = self.initialLiquidationLTV.toUint16(); 45 | 46 | unchecked { 47 | uint256 targetLiquidationLTV = self.liquidationLTV.toUint16(); 48 | uint256 timeRemaining = self.targetTimestamp - block.timestamp; 49 | 50 | // targetLiquidationLTV < initialLiquidationLTV and timeRemaining <= rampDuration 51 | currentLiquidationLTV = targetLiquidationLTV 52 | + (currentLiquidationLTV - targetLiquidationLTV) * timeRemaining / self.rampDuration; 53 | } 54 | 55 | // because ramping happens only when liquidation LTV decreases, it's safe to down-cast the new value 56 | return ConfigAmount.wrap(uint16(currentLiquidationLTV)); 57 | } 58 | 59 | function setLTV(LTVConfig memory self, ConfigAmount borrowLTV, ConfigAmount liquidationLTV, uint32 rampDuration) 60 | internal 61 | view 62 | returns (LTVConfig memory newLTV) 63 | { 64 | newLTV.borrowLTV = borrowLTV; 65 | newLTV.liquidationLTV = liquidationLTV; 66 | newLTV.initialLiquidationLTV = self.getLTV(true); 67 | newLTV.targetTimestamp = uint48(block.timestamp + rampDuration); 68 | newLTV.rampDuration = rampDuration; 69 | } 70 | } 71 | 72 | using LTVConfigLib for LTVConfig global; 73 | -------------------------------------------------------------------------------- /src/EVault/shared/types/Owed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {Owed, Assets, TypesLib} from "./Types.sol"; 6 | import "../Constants.sol"; 7 | 8 | /// @title OwedLib 9 | /// @custom:security-contact security@euler.xyz 10 | /// @author Euler Labs (https://www.eulerlabs.com/) 11 | /// @notice Library for `Owed` custom type 12 | /// @dev The owed type tracks borrowed funds in asset units scaled up by shifting left INTERNAL_DEBT_PRECISION_SHIFT 13 | /// bits. Increased precision allows for accurate interest accounting. 14 | library OwedLib { 15 | function toUint(Owed self) internal pure returns (uint256) { 16 | return Owed.unwrap(self); 17 | } 18 | 19 | function toAssetsUp(Owed amount) internal pure returns (Assets) { 20 | if (Owed.unwrap(amount) == 0) return Assets.wrap(0); 21 | 22 | return TypesLib.toAssets(toAssetsUpUint(Owed.unwrap(amount))); 23 | } 24 | 25 | function toAssetsDown(Owed amount) internal pure returns (Assets) { 26 | if (Owed.unwrap(amount) == 0) return Assets.wrap(0); 27 | 28 | return TypesLib.toAssets(Owed.unwrap(amount) >> INTERNAL_DEBT_PRECISION_SHIFT); 29 | } 30 | 31 | function isDust(Owed self) internal pure returns (bool) { 32 | // less than a minimum representable internal debt amount 33 | return Owed.unwrap(self) < (1 << INTERNAL_DEBT_PRECISION_SHIFT); 34 | } 35 | 36 | function isZero(Owed self) internal pure returns (bool) { 37 | return Owed.unwrap(self) == 0; 38 | } 39 | 40 | function mulDiv(Owed self, uint256 multiplier, uint256 divisor) internal pure returns (Owed) { 41 | return TypesLib.toOwed(uint256(Owed.unwrap(self)) * multiplier / divisor); 42 | } 43 | 44 | function addUnchecked(Owed self, Owed b) internal pure returns (Owed) { 45 | unchecked { 46 | return Owed.wrap(uint144(self.toUint() + b.toUint())); 47 | } 48 | } 49 | 50 | function subUnchecked(Owed self, Owed b) internal pure returns (Owed) { 51 | unchecked { 52 | return Owed.wrap(uint144(self.toUint() - b.toUint())); 53 | } 54 | } 55 | 56 | function toAssetsUpUint(uint256 owedExact) internal pure returns (uint256) { 57 | return (owedExact + (1 << INTERNAL_DEBT_PRECISION_SHIFT) - 1) >> INTERNAL_DEBT_PRECISION_SHIFT; 58 | } 59 | } 60 | 61 | function addOwed(Owed a, Owed b) pure returns (Owed) { 62 | return TypesLib.toOwed(uint256(Owed.unwrap(a)) + uint256(Owed.unwrap(b))); 63 | } 64 | 65 | function subOwed(Owed a, Owed b) pure returns (Owed) { 66 | return Owed.wrap((Owed.unwrap(a) - Owed.unwrap(b))); 67 | } 68 | 69 | function eqOwed(Owed a, Owed b) pure returns (bool) { 70 | return Owed.unwrap(a) == Owed.unwrap(b); 71 | } 72 | 73 | function neqOwed(Owed a, Owed b) pure returns (bool) { 74 | return Owed.unwrap(a) != Owed.unwrap(b); 75 | } 76 | 77 | function gtOwed(Owed a, Owed b) pure returns (bool) { 78 | return Owed.unwrap(a) > Owed.unwrap(b); 79 | } 80 | 81 | function ltOwed(Owed a, Owed b) pure returns (bool) { 82 | return Owed.unwrap(a) < Owed.unwrap(b); 83 | } 84 | -------------------------------------------------------------------------------- /src/EVault/shared/types/Shares.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {Shares, Assets, TypesLib} from "./Types.sol"; 6 | import {VaultCache} from "./VaultCache.sol"; 7 | import {ConversionHelpers} from "../lib/ConversionHelpers.sol"; 8 | 9 | /// @title SharesLib 10 | /// @custom:security-contact security@euler.xyz 11 | /// @author Euler Labs (https://www.eulerlabs.com/) 12 | /// @notice Library for `Shares` custom type, which is used to store vault's shares balances 13 | library SharesLib { 14 | function toUint(Shares self) internal pure returns (uint256) { 15 | return Shares.unwrap(self); 16 | } 17 | 18 | function isZero(Shares self) internal pure returns (bool) { 19 | return Shares.unwrap(self) == 0; 20 | } 21 | 22 | function toAssetsDown(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) { 23 | (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache); 24 | unchecked { 25 | return TypesLib.toAssets(amount.toUint() * totalAssets / totalShares); 26 | } 27 | } 28 | 29 | function toAssetsUp(Shares amount, VaultCache memory vaultCache) internal pure returns (Assets) { 30 | (uint256 totalAssets, uint256 totalShares) = ConversionHelpers.conversionTotals(vaultCache); 31 | unchecked { 32 | // totalShares >= VIRTUAL_DEPOSIT_AMOUNT > 1 33 | return TypesLib.toAssets((amount.toUint() * totalAssets + (totalShares - 1)) / totalShares); 34 | } 35 | } 36 | 37 | function mulDiv(Shares self, uint256 multiplier, uint256 divisor) internal pure returns (Shares) { 38 | return TypesLib.toShares(uint256(Shares.unwrap(self)) * multiplier / divisor); 39 | } 40 | 41 | function subUnchecked(Shares self, Shares b) internal pure returns (Shares) { 42 | unchecked { 43 | return Shares.wrap(uint112(self.toUint() - b.toUint())); 44 | } 45 | } 46 | } 47 | 48 | function addShares(Shares a, Shares b) pure returns (Shares) { 49 | return TypesLib.toShares(uint256(Shares.unwrap(a)) + uint256(Shares.unwrap(b))); 50 | } 51 | 52 | function subShares(Shares a, Shares b) pure returns (Shares) { 53 | return Shares.wrap((Shares.unwrap(a) - Shares.unwrap(b))); 54 | } 55 | 56 | function eqShares(Shares a, Shares b) pure returns (bool) { 57 | return Shares.unwrap(a) == Shares.unwrap(b); 58 | } 59 | 60 | function neqShares(Shares a, Shares b) pure returns (bool) { 61 | return Shares.unwrap(a) != Shares.unwrap(b); 62 | } 63 | 64 | function gtShares(Shares a, Shares b) pure returns (bool) { 65 | return Shares.unwrap(a) > Shares.unwrap(b); 66 | } 67 | 68 | function ltShares(Shares a, Shares b) pure returns (bool) { 69 | return Shares.unwrap(a) < Shares.unwrap(b); 70 | } 71 | -------------------------------------------------------------------------------- /src/EVault/shared/types/Snapshot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {Assets} from "./Types.sol"; 6 | 7 | /// @title Snapshot 8 | /// @notice This struct is used to store a snapshot of the vault's cash and total borrows at the beginning of an 9 | /// operation (or a batch thereof) 10 | struct Snapshot { 11 | // Packed slot: 14 + 14 + 4 = 32 12 | // vault's cash holdings 13 | Assets cash; 14 | // vault's total borrows in assets, in regular precision 15 | Assets borrows; 16 | // stamp occupies the rest of the storage slot and makes sure the slot is non-zero for gas savings 17 | uint32 stamp; 18 | } 19 | 20 | /// @title SnapshotLib 21 | /// @custom:security-contact security@euler.xyz 22 | /// @author Euler Labs (https://www.eulerlabs.com/) 23 | /// @notice Library for working with the `Snapshot` struct 24 | library SnapshotLib { 25 | uint32 private constant STAMP = 1; // non zero initial value of the snapshot slot to save gas on SSTORE 26 | 27 | function set(Snapshot storage self, Assets cash, Assets borrows) internal { 28 | self.cash = cash; 29 | self.borrows = borrows; 30 | self.stamp = STAMP; 31 | } 32 | 33 | function reset(Snapshot storage self) internal { 34 | self.set(Assets.wrap(0), Assets.wrap(0)); 35 | } 36 | } 37 | 38 | using SnapshotLib for Snapshot global; 39 | -------------------------------------------------------------------------------- /src/EVault/shared/types/Types.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../IEVault.sol"; 6 | 7 | import "./VaultStorage.sol"; 8 | import "./Snapshot.sol"; 9 | import "./UserStorage.sol"; 10 | 11 | import "./Shares.sol"; 12 | import "./Assets.sol"; 13 | import "./Owed.sol"; 14 | import "./ConfigAmount.sol"; 15 | import "./Flags.sol"; 16 | import "./AmountCap.sol"; 17 | 18 | /// @notice In this file, custom types are defined and linked globally with their libraries and operators 19 | 20 | type Shares is uint112; 21 | 22 | type Assets is uint112; 23 | 24 | type Owed is uint144; 25 | 26 | type AmountCap is uint16; 27 | 28 | type ConfigAmount is uint16; 29 | 30 | type Flags is uint32; 31 | 32 | using SharesLib for Shares global; 33 | using { 34 | addShares as +, subShares as -, eqShares as ==, neqShares as !=, gtShares as >, ltShares as < 35 | } for Shares global; 36 | 37 | using AssetsLib for Assets global; 38 | using { 39 | addAssets as +, 40 | subAssets as -, 41 | eqAssets as ==, 42 | neqAssets as !=, 43 | gtAssets as >, 44 | gteAssets as >=, 45 | ltAssets as <, 46 | lteAssets as <= 47 | } for Assets global; 48 | 49 | using OwedLib for Owed global; 50 | using {addOwed as +, subOwed as -, eqOwed as ==, neqOwed as !=, gtOwed as >, ltOwed as <} for Owed global; 51 | 52 | using ConfigAmountLib for ConfigAmount global; 53 | using { 54 | gtConfigAmount as >, gteConfigAmount as >=, ltConfigAmount as <, lteConfigAmount as <= 55 | } for ConfigAmount global; 56 | 57 | using AmountCapLib for AmountCap global; 58 | using FlagsLib for Flags global; 59 | 60 | /// @title TypesLib 61 | /// @notice Library for casting basic types' amounts into custom types 62 | library TypesLib { 63 | function toShares(uint256 amount) internal pure returns (Shares) { 64 | if (amount > MAX_SANE_AMOUNT) revert Errors.E_AmountTooLargeToEncode(); 65 | return Shares.wrap(uint112(amount)); 66 | } 67 | 68 | function toAssets(uint256 amount) internal pure returns (Assets) { 69 | if (amount > MAX_SANE_AMOUNT) revert Errors.E_AmountTooLargeToEncode(); 70 | return Assets.wrap(uint112(amount)); 71 | } 72 | 73 | function toOwed(uint256 amount) internal pure returns (Owed) { 74 | if (amount > MAX_SANE_DEBT_AMOUNT) revert Errors.E_DebtAmountTooLargeToEncode(); 75 | return Owed.wrap(uint144(amount)); 76 | } 77 | 78 | function toConfigAmount(uint16 amount) internal pure returns (ConfigAmount) { 79 | if (amount > CONFIG_SCALE) revert Errors.E_ConfigAmountTooLargeToEncode(); 80 | return ConfigAmount.wrap(amount); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/EVault/shared/types/VaultCache.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {IERC20} from "../../IEVault.sol"; 6 | import {IPriceOracle} from "../../../interfaces/IPriceOracle.sol"; 7 | 8 | import {Assets, Owed, Shares, Flags} from "./Types.sol"; 9 | 10 | /// @title VaultCache 11 | /// @notice This struct is used to hold all the most often used vault data in memory 12 | struct VaultCache { 13 | // Proxy immutables 14 | 15 | // Vault's asset 16 | IERC20 asset; 17 | // Vault's pricing oracle 18 | IPriceOracle oracle; 19 | // Unit of account is the asset in which collateral and liability values are expressed 20 | address unitOfAccount; 21 | 22 | // Vault data 23 | 24 | // A timestamp of the last interest accumulator update 25 | uint48 lastInterestAccumulatorUpdate; 26 | // The amount of assets held directly by the vault 27 | Assets cash; 28 | // Sum of all user debts 29 | Owed totalBorrows; 30 | // Sum of all user shares 31 | Shares totalShares; 32 | // Interest fees accrued since the last fee conversion 33 | Shares accumulatedFees; 34 | // Current interest accumulator 35 | uint256 interestAccumulator; 36 | 37 | // Vault config 38 | 39 | // Current supply cap in asset units 40 | uint256 supplyCap; 41 | // Current borrow cap in asset units 42 | uint256 borrowCap; 43 | // A bitfield of operations which trigger a hook call 44 | Flags hookedOps; 45 | // A bitfield of vault configuration options 46 | Flags configFlags; 47 | 48 | // Runtime 49 | 50 | // A flag indicating if the vault snapshot has already been initialized for the currently executing batch 51 | bool snapshotInitialized; 52 | } 53 | -------------------------------------------------------------------------------- /src/EVault/shared/types/VaultStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {Assets, Shares, Owed, AmountCap, ConfigAmount, Flags} from "./Types.sol"; 6 | import {LTVConfig} from "./LTVConfig.sol"; 7 | import {UserStorage} from "./UserStorage.sol"; 8 | 9 | /// @title VaultStorage 10 | /// @notice This struct is used to hold all of the vault's permanent storage 11 | /// @dev Note that snapshots are not a part of this struct, as they might be reimplemented as transient storage 12 | struct VaultStorage { 13 | // Packed slot 6 + 14 + 2 + 2 + 4 + 1 + 1 = 30 14 | // A timestamp of the last interest accumulator update 15 | uint48 lastInterestAccumulatorUpdate; 16 | // The amount of assets held directly by the vault 17 | Assets cash; 18 | // Current supply cap in asset units 19 | AmountCap supplyCap; 20 | // Current borrow cap in asset units 21 | AmountCap borrowCap; 22 | // A bitfield of operations which trigger a hook call 23 | Flags hookedOps; 24 | // A vault global reentrancy protection flag 25 | bool reentrancyLocked; 26 | // A flag indicating if the vault snapshot has already been initialized for the currently executing batch 27 | bool snapshotInitialized; 28 | 29 | // Packed slot 14 + 18 = 32 30 | // Sum of all user shares 31 | Shares totalShares; 32 | // Sum of all user debts 33 | Owed totalBorrows; 34 | 35 | // Packed slot 14 + 2 + 2 + 4 = 22 36 | // Interest fees accrued since the last fee conversion 37 | Shares accumulatedFees; 38 | // Maximum liquidation discount 39 | ConfigAmount maxLiquidationDiscount; 40 | // Amount of time in seconds that must pass after a successful account status check before liquidation is possible 41 | uint16 liquidationCoolOffTime; 42 | // A bitfield of vault configuration options 43 | Flags configFlags; 44 | 45 | // Current interest accumulator 46 | uint256 interestAccumulator; 47 | 48 | // Packed slot 20 + 2 + 9 = 31 49 | // Address of the interest rate model contract. If not set, 0% interest is applied 50 | address interestRateModel; 51 | // Percentage of accrued interest that is directed to fees 52 | ConfigAmount interestFee; 53 | // Current interest rate applied to outstanding borrows 54 | uint72 interestRate; 55 | 56 | // Name of the shares token (eToken) 57 | string name; 58 | // Symbol of the shares token (eToken) 59 | string symbol; 60 | 61 | // Address of the vault's creator 62 | address creator; 63 | 64 | // Address of the vault's governor 65 | address governorAdmin; 66 | // Address which receives governor fees 67 | address feeReceiver; 68 | // Address which will be called for enabled hooks 69 | address hookTarget; 70 | 71 | // User accounts 72 | mapping(address account => UserStorage) users; 73 | 74 | // LTV configuration for collaterals 75 | mapping(address collateral => LTVConfig) ltvLookup; 76 | // List of addresses which were at any point configured as collateral 77 | address[] ltvList; 78 | } 79 | -------------------------------------------------------------------------------- /src/GenericFactory/MetaProxyDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /// @title MetaProxyDeployer 6 | /// @custom:security-contact security@euler.xyz 7 | /// @author Euler Labs (https://www.eulerlabs.com/) 8 | /// @notice Contract for deploying minimal proxies with metadata, based on EIP-3448. 9 | /// @dev The metadata of the proxies does not include the data length as defined by EIP-3448, saving gas at a cost of 10 | /// supporting variable size data. 11 | contract MetaProxyDeployer { 12 | error E_DeploymentFailed(); 13 | 14 | // Meta proxy bytecode from EIP-3488 https://eips.ethereum.org/EIPS/eip-3448 15 | bytes constant BYTECODE_HEAD = hex"600b380380600b3d393df3363d3d373d3d3d3d60368038038091363936013d73"; 16 | bytes constant BYTECODE_TAIL = hex"5af43d3d93803e603457fd5bf3"; 17 | 18 | /// @dev Creates a proxy for `targetContract` with metadata from `metadata`. 19 | /// @return addr A non-zero address if successful. 20 | function deployMetaProxy(address targetContract, bytes memory metadata) internal returns (address addr) { 21 | bytes memory code = abi.encodePacked(BYTECODE_HEAD, targetContract, BYTECODE_TAIL, metadata); 22 | 23 | assembly ("memory-safe") { 24 | addr := create(0, add(code, 32), mload(code)) 25 | } 26 | 27 | if (addr == address(0)) revert E_DeploymentFailed(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/InterestRateModels/IIRM.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /// @title IIRM 6 | /// @custom:security-contact security@euler.xyz 7 | /// @author Euler Labs (https://www.eulerlabs.com/) 8 | /// @notice Interface of the interest rate model contracts used by EVault 9 | interface IIRM { 10 | error E_IRMUpdateUnauthorized(); 11 | 12 | /// @notice Perform potentially state mutating computation of the new interest rate 13 | /// @param vault Address of the vault to compute the new interest rate for 14 | /// @param cash Amount of assets held directly by the vault 15 | /// @param borrows Amount of assets lent out to borrowers by the vault 16 | /// @return Then new interest rate in second percent yield (SPY), scaled by 1e27 17 | function computeInterestRate(address vault, uint256 cash, uint256 borrows) external returns (uint256); 18 | 19 | /// @notice Perform computation of the new interest rate without mutating state 20 | /// @param vault Address of the vault to compute the new interest rate for 21 | /// @param cash Amount of assets held directly by the vault 22 | /// @param borrows Amount of assets lent out to borrowers by the vault 23 | /// @return Then new interest rate in second percent yield (SPY), scaled by 1e27 24 | function computeInterestRateView(address vault, uint256 cash, uint256 borrows) external view returns (uint256); 25 | } 26 | -------------------------------------------------------------------------------- /src/InterestRateModels/IRMLinearKink.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./IIRM.sol"; 6 | 7 | /// @title IRMLinearKink 8 | /// @custom:security-contact security@euler.xyz 9 | /// @author Euler Labs (https://www.eulerlabs.com/) 10 | /// @notice Implementation of an interest rate model, where interest rate grows linearly with utilization, and spikes 11 | /// after reaching kink 12 | contract IRMLinearKink is IIRM { 13 | /// @notice Base interest rate applied when utilization is equal zero 14 | uint256 public immutable baseRate; 15 | /// @notice Slope of the function before the kink 16 | uint256 public immutable slope1; 17 | /// @notice Slope of the function after the kink 18 | uint256 public immutable slope2; 19 | /// @notice Utilization at which the slope of the interest rate function changes. In type(uint32).max scale. 20 | uint256 public immutable kink; 21 | 22 | constructor(uint256 baseRate_, uint256 slope1_, uint256 slope2_, uint32 kink_) { 23 | baseRate = baseRate_; 24 | slope1 = slope1_; 25 | slope2 = slope2_; 26 | kink = kink_; 27 | } 28 | 29 | /// @inheritdoc IIRM 30 | function computeInterestRate(address vault, uint256 cash, uint256 borrows) 31 | external 32 | view 33 | override 34 | returns (uint256) 35 | { 36 | if (msg.sender != vault) revert E_IRMUpdateUnauthorized(); 37 | 38 | return computeInterestRateInternal(vault, cash, borrows); 39 | } 40 | 41 | /// @inheritdoc IIRM 42 | function computeInterestRateView(address vault, uint256 cash, uint256 borrows) 43 | external 44 | view 45 | override 46 | returns (uint256) 47 | { 48 | return computeInterestRateInternal(vault, cash, borrows); 49 | } 50 | 51 | function computeInterestRateInternal(address, uint256 cash, uint256 borrows) internal view returns (uint256) { 52 | uint256 totalAssets = cash + borrows; 53 | 54 | uint32 utilization = totalAssets == 0 55 | ? 0 // empty pool arbitrarily given utilization of 0 56 | : uint32(borrows * type(uint32).max / totalAssets); 57 | 58 | uint256 ir = baseRate; 59 | 60 | if (utilization <= kink) { 61 | ir += utilization * slope1; 62 | } else { 63 | ir += kink * slope1; 64 | 65 | uint256 utilizationOverKink; 66 | unchecked { 67 | utilizationOverKink = utilization - kink; 68 | } 69 | ir += slope2 * utilizationOverKink; 70 | } 71 | 72 | return ir; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/ProtocolConfig/IProtocolConfig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /// @title IProtocolConfig 6 | /// @custom:security-contact security@euler.xyz 7 | /// @author Euler Labs (https://www.eulerlabs.com/) 8 | /// @notice Interface of the contract centralizing the protocol's (DAO's) configuration for all the EVault deployments 9 | interface IProtocolConfig { 10 | /// @notice check if a vault's interest fee is valid 11 | /// @param vault address of the vault 12 | /// @param interestFee an interest fee value to check 13 | /// @dev an interest fee is considered valid only when it is greater than or equal the min interest fee and less 14 | /// than or equal the max interest fee 15 | /// @dev if a vault has a specific interest fee ranges set by admin, it will be used, otherwise the generic ones 16 | /// will be checked against 17 | /// @return bool true for valid, else false 18 | function isValidInterestFee(address vault, uint16 interestFee) external view returns (bool); 19 | 20 | /// @notice get protocol fee config for a certain vault 21 | /// @param vault address of the vault 22 | /// @dev if vault == address(0), the generic config will be returned 23 | /// @return address protocol fee receiver 24 | /// @return uint16 protocol fee share 25 | function protocolFeeConfig(address vault) external view returns (address, uint16); 26 | 27 | /// @notice get interest fee ranges for a certain vault 28 | /// @param vault address of the vault 29 | /// @dev if vault == address(0), the generic ranges will be returned 30 | /// @return uint16 min interest fee 31 | /// @return uint16 max interest fee 32 | function interestFeeRange(address vault) external view returns (uint16, uint16); 33 | } 34 | -------------------------------------------------------------------------------- /src/SequenceRegistry/SequenceRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {ISequenceRegistry} from "../interfaces/ISequenceRegistry.sol"; 6 | 7 | /// @title SequenceRegistry 8 | /// @custom:security-contact security@euler.xyz 9 | /// @author Euler Labs (https://www.eulerlabs.com/) 10 | /// @notice This contract maintains sequence counters associated with opaque designator strings. 11 | /// Each counter starts at 1. 12 | /// @dev Anybody can reserve a sequence ID. The only guarantee provided is that no two reservations for the same 13 | /// designator will get the same ID. 14 | contract SequenceRegistry is ISequenceRegistry { 15 | /// @dev Each designator maps to the previous sequence ID issued, or 0 if none were ever issued. 16 | mapping(string designator => uint256 lastSeqId) public counters; 17 | 18 | /// @notice A sequence ID has been reserved 19 | /// @param designator The opaque designator string 20 | /// @param id The reserved ID, which is unique per designator 21 | /// @param caller The msg.sender who reserved the ID 22 | event SequenceIdReserved(string designator, uint256 indexed id, address indexed caller); 23 | 24 | /// @inheritdoc ISequenceRegistry 25 | function reserveSeqId(string calldata designator) external returns (uint256) { 26 | uint256 seqId = ++counters[designator]; 27 | 28 | emit SequenceIdReserved(designator, seqId, msg.sender); 29 | 30 | return seqId; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Synths/ERC20EVCCompatible.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {ERC20, Context} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; 6 | import {ERC20Permit} from "openzeppelin-contracts/token/ERC20/extensions/ERC20Permit.sol"; 7 | import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol"; 8 | 9 | /// @title ERC20EVCCompatible 10 | /// @custom:security-contact security@euler.xyz 11 | /// @author Euler Labs (https://www.eulerlabs.com/) 12 | /// @notice ERC20EVCCompatible is an ERC20-compatible token with the EVC support. 13 | abstract contract ERC20EVCCompatible is EVCUtil, ERC20Permit { 14 | constructor(address _evc_, string memory _name_, string memory _symbol_) 15 | EVCUtil(_evc_) 16 | ERC20(_name_, _symbol_) 17 | ERC20Permit(_name_) 18 | {} 19 | 20 | /// @notice Retrieves the message sender in the context of the EVC. 21 | /// @dev Overridden due to the conflict with the Context definition. 22 | /// @dev This function returns the account on behalf of which the current operation is being performed, which is 23 | /// either msg.sender or the account authenticated by the EVC. 24 | /// @return The address of the message sender. 25 | function _msgSender() internal view virtual override (EVCUtil, Context) returns (address) { 26 | return EVCUtil._msgSender(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Synths/HookTargetSynth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {IHookTarget} from "../interfaces/IHookTarget.sol"; 6 | import {IEVault} from "../EVault/IEVault.sol"; 7 | 8 | /// @title HookTargetSynth 9 | /// @custom:security-contact security@euler.xyz 10 | /// @author Euler Labs (https://www.eulerlabs.com/) 11 | /// @notice HookTargetSynth is designed to block unnecessary operations and enforce deposits only by the asset contract. 12 | contract HookTargetSynth is IHookTarget { 13 | error E_OnlyAssetCanDeposit(); 14 | error E_OperationDisabled(); 15 | 16 | function isHookTarget() external pure override returns (bytes4) { 17 | return this.isHookTarget.selector; 18 | } 19 | 20 | // deposit is only allowed for the asset itself 21 | function deposit(uint256, address) external view { 22 | if (IEVault(msg.sender).asset() != caller()) revert E_OnlyAssetCanDeposit(); 23 | } 24 | 25 | // all the other hooked operations are disabled 26 | fallback() external { 27 | revert E_OperationDisabled(); 28 | } 29 | 30 | function caller() internal pure returns (address _caller) { 31 | assembly { 32 | _caller := shr(96, calldataload(sub(calldatasize(), 20))) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/interfaces/IBalanceTracker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /// @title IBalanceTracker 6 | /// @custom:security-contact security@euler.xyz 7 | /// @author Euler Labs (https://www.eulerlabs.com/) 8 | /// @notice Provides an interface for tracking the balance of accounts. 9 | interface IBalanceTracker { 10 | /// @notice Executes the balance tracking hook for an account. 11 | /// @dev This function is called by the Balance Forwarder contract which was enabled for the account. This function 12 | /// must be called with the current balance of the account when enabling the balance forwarding for it. This 13 | /// function must be called with 0 balance of the account when disabling the balance forwarding for it. This 14 | /// function allows to be called on zero balance transfers, when the newAccountBalance is the same as the previous 15 | /// one. To prevent DOS attacks, forfeitRecentReward should be used appropriately. 16 | /// @param account The account address to execute the hook for. 17 | /// @param newAccountBalance The new balance of the account. 18 | /// @param forfeitRecentReward Whether to forfeit the most recent reward and not update the accumulator. 19 | function balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external; 20 | } 21 | -------------------------------------------------------------------------------- /src/interfaces/IFlashLoan.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /// @title IFlashLoan 6 | /// @custom:security-contact security@euler.xyz 7 | /// @author Euler Labs (https://www.eulerlabs.com/) 8 | /// @notice Definition of callback method that flashLoan will invoke on your contract 9 | interface IFlashLoan { 10 | /// @notice Function that will be called on the caller of `flashloan` 11 | /// @param data Data that was passed to the `flashloan` call 12 | function onFlashLoan(bytes memory data) external; 13 | } 14 | -------------------------------------------------------------------------------- /src/interfaces/IHookTarget.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /// @title IHookTarget 6 | /// @author Euler Labs (https://www.eulerlabs.com/) 7 | /// @custom:security-contact security@euler.xyz 8 | /// @notice Provides an interface for the hook target contract 9 | interface IHookTarget { 10 | /// @notice If given contract is a hook target, it is expected to return the bytes4 magic value that is the selector 11 | /// of this function 12 | /// @return The bytes4 magic value (0x87439e04) that is the selector of this function 13 | function isHookTarget() external view returns (bytes4); 14 | } 15 | -------------------------------------------------------------------------------- /src/interfaces/IPermit2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /// @title IPermit2 6 | /// @custom:security-contact security@euler.xyz 7 | /// @author Euler Labs (https://www.eulerlabs.com/) 8 | /// @notice A minimal interface of the Uniswap's Permit2 contract 9 | interface IPermit2 { 10 | /// @notice Transfer tokens between two accounts 11 | /// @param from The account to send the tokens from 12 | /// @param to The account to send the tokens to 13 | /// @param amount Amount of tokens to send 14 | /// @param token Address of the token contract 15 | function transferFrom(address from, address to, uint160 amount, address token) external; 16 | } 17 | -------------------------------------------------------------------------------- /src/interfaces/IPriceOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /// @title IPriceOracle 6 | /// @custom:security-contact security@euler.xyz 7 | /// @author Euler Labs (https://www.eulerlabs.com/) 8 | /// @notice Common PriceOracle interface. 9 | interface IPriceOracle { 10 | /// @notice Get the name of the oracle. 11 | /// @return The name of the oracle. 12 | function name() external view returns (string memory); 13 | 14 | /// @notice One-sided price: How much quote token you would get for inAmount of base token, assuming no price 15 | /// spread. 16 | /// @param inAmount The amount of `base` to convert. 17 | /// @param base The token that is being priced. 18 | /// @param quote The token that is the unit of account. 19 | /// @return outAmount The amount of `quote` that is equivalent to `inAmount` of `base`. 20 | function getQuote(uint256 inAmount, address base, address quote) external view returns (uint256 outAmount); 21 | 22 | /// @notice Two-sided price: How much quote token you would get/spend for selling/buying inAmount of base token. 23 | /// @param inAmount The amount of `base` to convert. 24 | /// @param base The token that is being priced. 25 | /// @param quote The token that is the unit of account. 26 | /// @return bidOutAmount The amount of `quote` you would get for selling `inAmount` of `base`. 27 | /// @return askOutAmount The amount of `quote` you would spend for buying `inAmount` of `base`. 28 | function getQuotes(uint256 inAmount, address base, address quote) 29 | external 30 | view 31 | returns (uint256 bidOutAmount, uint256 askOutAmount); 32 | } 33 | -------------------------------------------------------------------------------- /src/interfaces/ISequenceRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /// @title ISequenceRegistry 6 | /// @custom:security-contact security@euler.xyz 7 | /// @author Euler Labs (https://www.eulerlabs.com/) 8 | /// @notice Provides an interface for reserving sequence IDs. 9 | interface ISequenceRegistry { 10 | /// @notice Reserve an ID for a given designator 11 | /// @param designator An opaque string that corresponds to the sequence counter to be used 12 | /// @return Sequence ID 13 | function reserveSeqId(string calldata designator) external returns (uint256); 14 | } 15 | -------------------------------------------------------------------------------- /test/helpers/AssertionsCustomTypes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/EVault/shared/types/Types.sol"; 6 | import "forge-std/StdAssertions.sol"; 7 | 8 | /// @notice assertion helpers for custom types 9 | contract AssertionsCustomTypes is StdAssertions { 10 | Assets constant ZERO_ASSETS = Assets.wrap(0); 11 | Shares constant ZERO_SHARES = Shares.wrap(0); 12 | Owed constant ZERO_OWED = Owed.wrap(0); 13 | 14 | Assets constant MAX_ASSETS = Assets.wrap(uint112(MAX_SANE_AMOUNT)); 15 | Shares constant MAX_SHARES = Shares.wrap(uint112(MAX_SANE_AMOUNT)); 16 | Owed constant MAX_OWED = Owed.wrap(uint144(MAX_SANE_DEBT_AMOUNT)); 17 | 18 | function assertEq(Assets a, Assets b) internal pure { 19 | assertEq(Assets.unwrap(a), Assets.unwrap(b)); 20 | } 21 | 22 | function assertEq(Assets a, Assets b, string memory err) internal pure { 23 | assertEq(Assets.unwrap(a), Assets.unwrap(b), err); 24 | } 25 | 26 | function assertEq(Shares a, Shares b) internal pure { 27 | assertEq(Shares.unwrap(a), Shares.unwrap(b)); 28 | } 29 | 30 | function assertEq(Shares a, Shares b, string memory err) internal pure { 31 | assertEq(Shares.unwrap(a), Shares.unwrap(b), err); 32 | } 33 | 34 | function assertEq(Owed a, Owed b) internal pure { 35 | assertEq(Owed.unwrap(a), Owed.unwrap(b)); 36 | } 37 | 38 | function assertEq(Owed a, Owed b, string memory err) internal pure { 39 | assertEq(Owed.unwrap(a), Owed.unwrap(b), err); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/helpers/MathTesting.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.13; 3 | 4 | contract MathTesting { 5 | //FROM---abdk-libraries-solidity/ABDKMath64x64.sol----------- 6 | /** 7 | * Calculate binary logarithm of x. Revert if x <= 0. 8 | * 9 | * @param x signed 64.64-bit fixed point number // x = n * 2**64 10 | * @return signed 64.64-bit fixed point number 11 | */ 12 | function log_2(int128 x) public pure returns (int128) { 13 | unchecked { 14 | require(x > 0); 15 | 16 | int256 msb = 0; 17 | int256 xc = x; 18 | if (xc >= 0x10000000000000000) { 19 | xc >>= 64; 20 | msb += 64; 21 | } 22 | if (xc >= 0x100000000) { 23 | xc >>= 32; 24 | msb += 32; 25 | } 26 | if (xc >= 0x10000) { 27 | xc >>= 16; 28 | msb += 16; 29 | } 30 | if (xc >= 0x100) { 31 | xc >>= 8; 32 | msb += 8; 33 | } 34 | if (xc >= 0x10) { 35 | xc >>= 4; 36 | msb += 4; 37 | } 38 | if (xc >= 0x4) { 39 | xc >>= 2; 40 | msb += 2; 41 | } 42 | if (xc >= 0x2) msb += 1; // No need to shift xc anymore 43 | 44 | int256 result = msb - 64 << 64; 45 | uint256 ux = uint256(int256(x)) << uint256(127 - msb); 46 | for (int256 bit = 0x8000000000000000; bit > 0; bit >>= 1) { 47 | ux *= ux; 48 | uint256 b = ux >> 255; 49 | ux >>= 127 + b; 50 | result += bit * int256(b); 51 | } 52 | 53 | return int128(result); 54 | } 55 | } 56 | 57 | /** 58 | * Calculate natural logarithm of x. Revert if x <= 0. 59 | * 60 | * @param x signed 64.64-bit fixed point number // x = n * 2**64 61 | * @return signed 64.64-bit fixed point number 62 | */ 63 | function ln(int128 x) public pure returns (int128) { 64 | unchecked { 65 | require(x > 0); 66 | 67 | return int128(int256(uint256(int256(log_2(x))) * 0xB17217F7D1CF79ABC9E3B39803F2F6AF >> 128)); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/invariants/HandlerAggregator.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Modules Handler Contracts 5 | import {VaultModuleHandler} from "./handlers/modules/VaultModuleHandler.t.sol"; 6 | import {BorrowingModuleHandler} from "./handlers/modules/BorrowingModuleHandler.t.sol"; 7 | import {LiquidationModuleHandler} from "./handlers/modules/LiquidationModuleHandler.t.sol"; 8 | import {EVCHandler} from "./handlers/external/EVCHandler.t.sol"; 9 | import {TokenModuleHandler} from "./handlers/modules/TokenModuleHandler.t.sol"; 10 | import {RiskManagerModuleHandler} from "./handlers/modules/RiskManagerModuleHandler.t.sol"; 11 | import {BalanceForwarderModuleHandler} from "./handlers/modules/BalanceForwarderModuleHandler.t.sol"; 12 | import {GovernanceModuleHandler} from "./handlers/modules/GovernanceModuleHandler.t.sol"; 13 | 14 | // Simulators 15 | import {DonationAttackHandler} from "./handlers/simulators/DonationAttackHandler.t.sol"; 16 | import {FlashLoanHandler} from "./handlers/simulators/FlashLoanHandler.t.sol"; 17 | import {IRMHandler} from "./handlers/simulators/IRMHandler.t.sol"; 18 | import {PriceOracleHandler} from "./handlers/simulators/PriceOracleHandler.t.sol"; 19 | 20 | /// @notice Helper contract to aggregate all handler contracts, inherited in BaseInvariants 21 | abstract contract HandlerAggregator is 22 | TokenModuleHandler, // Module handlers 23 | VaultModuleHandler, 24 | BorrowingModuleHandler, 25 | LiquidationModuleHandler, 26 | RiskManagerModuleHandler, 27 | BalanceForwarderModuleHandler, 28 | GovernanceModuleHandler, 29 | EVCHandler, // EVC handler 30 | DonationAttackHandler, // Simulator handlers 31 | FlashLoanHandler, 32 | IRMHandler, 33 | PriceOracleHandler 34 | { 35 | /// @notice Helper function in case any handler requires additional setup 36 | function _setUpHandlers() internal {} 37 | } 38 | -------------------------------------------------------------------------------- /test/invariants/Tester.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Test Contracts 5 | import {Invariants} from "./Invariants.t.sol"; 6 | import {Setup} from "./Setup.t.sol"; 7 | 8 | /// @title Tester 9 | /// @notice Entry point for invariant testing, inherits all contracts, invariants & handler 10 | /// @dev Mono contract that contains all the testing logic 11 | contract Tester is Invariants, Setup { 12 | constructor() payable { 13 | setUp(); 14 | } 15 | 16 | /// @dev Foundry compatibility faster setup debugging 17 | function setUp() internal { 18 | // Deploy protocol contracts and protocol actors 19 | _setUp(); 20 | 21 | // Deploy actors 22 | _setUpActors(); 23 | 24 | // Initialize handler contracts 25 | _setUpHandlers(); 26 | } 27 | 28 | /// @dev Needed in order for foundry to recognise the contract as a test, faster debugging 29 | //function testAux() public view {} 30 | } 31 | -------------------------------------------------------------------------------- /test/invariants/_config/echidna_config.yaml: -------------------------------------------------------------------------------- 1 | #codeSize max code size for deployed contratcs (default 24576, per EIP-170) 2 | codeSize: 224576 3 | 4 | #whether ot not to use the multi-abi mode of testing 5 | #it’s not working for us, see: https://github.com/crytic/echidna/issues/547 6 | #multi-abi: true 7 | 8 | #balanceAddr is default balance for addresses 9 | balanceAddr: 0xffffffffffffffffffffffff 10 | #balanceContract overrides balanceAddr for the contract address (2^128 = ~3e38) 11 | balanceContract: 0xffffffffffffffffffffffffffffffffffffffffffffffff 12 | 13 | #testLimit is the number of test sequences to run 14 | testLimit: 20000000 15 | 16 | #seqLen defines how many transactions are in a test sequence 17 | seqLen: 300 18 | 19 | #shrinkLimit determines how much effort is spent shrinking failing sequences 20 | shrinkLimit: 2500 21 | 22 | #propMaxGas defines gas cost at which a property fails 23 | propMaxGas: 1000000000 24 | 25 | #testMaxGas is a gas limit; does not cause failure, but terminates sequence 26 | testMaxGas: 1000000000 27 | 28 | # list of methods to filter 29 | #filterFunctions: ["openCdpExt"] 30 | # by default, blacklist methods in filterFunctions 31 | #filterBlacklist: false 32 | 33 | #stopOnFail makes echidna terminate as soon as any property fails and has been shrunk 34 | stopOnFail: false 35 | 36 | #coverage controls coverage guided testing 37 | coverage: true 38 | 39 | # list of file formats to save coverage reports in; default is all possible formats 40 | coverageFormats: ["lcov", "html"] 41 | 42 | #directory to save the corpus; by default is disabled 43 | corpusDir: "test/invariants/_corpus/echidna/default/_data/corpus" 44 | # constants for corpus mutations (for experimentation only) 45 | #mutConsts: [100, 1, 1] 46 | 47 | #remappings 48 | cryticArgs: ["--solc-remaps", "@crytic/properties/=lib/properties/ forge-std/=lib/forge-std/src/ ds-test/=lib/forge-std/lib/ds-test/src/ ethereum-vault-connector/=lib/ethereum-vault-connector/src openzeppelin/=lib/openzeppelin-contracts/contracts/", "--compile-libraries=(Pretty,0xf01),(Strings,0xf02)"] 49 | 50 | deployContracts: [["0xf01", "Pretty"], ["0xf02", "Strings"]] 51 | 52 | # maximum value to send to payable functions 53 | maxValue: 100000000000000000000000 # 100000 eth 54 | 55 | #quiet produces (much) less verbose output 56 | quiet: false 57 | 58 | # concurrent workers 59 | workers: 10 60 | -------------------------------------------------------------------------------- /test/invariants/base/BaseHooks.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Contracts 5 | import {ProtocolAssertions} from "./ProtocolAssertions.t.sol"; 6 | 7 | // Test Contracts 8 | import {InvariantsSpec} from "../InvariantsSpec.t.sol"; 9 | 10 | /// @title BaseHooks 11 | /// @notice Contains common logic for all handlers 12 | /// @dev inherits all suite assertions since per-action assertions are implemented in the handlers 13 | /// @dev inherits the Invariant Specifications contract 14 | contract BaseHooks is ProtocolAssertions, InvariantsSpec { 15 | /////////////////////////////////////////////////////////////////////////////////////////////// 16 | // HELPERS // 17 | /////////////////////////////////////////////////////////////////////////////////////////////// 18 | 19 | /// @notice Calculates the exchange rate for the eTST vault 20 | function _calculateExchangeRate() internal view returns (uint256) { 21 | return (eTST.totalAssets() + VIRTUAL_DEPOSIT_AMOUNT) / (eTST.totalSupply() + VIRTUAL_DEPOSIT_AMOUNT); 22 | } 23 | 24 | function _getHealthScore(uint256 liabilityValue, uint256 collateralValue) internal pure returns (uint256) { 25 | return liabilityValue == 0 ? 1e18 : collateralValue * 1e18 / liabilityValue; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/invariants/base/ProtocolAssertions.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Base 5 | import {BaseTest} from "./BaseTest.t.sol"; 6 | import {StdAsserts} from "../utils/StdAsserts.sol"; 7 | 8 | /// @title ProtocolAssertions 9 | /// @notice Helper contract for protocol specific assertions 10 | abstract contract ProtocolAssertions is StdAsserts, BaseTest { 11 | /// @notice Returns true if an account is healthy (liability <= collateral) 12 | function isAccountHealthy(uint256 _liability, uint256 _collateral) internal pure returns (bool) { 13 | return _liability <= _collateral; 14 | } 15 | 16 | /// @notice Checks whether the account is healthy from a BORROWING perspective 17 | function isAccountHealthy(address _account) internal view returns (bool) { 18 | (uint256 collateralValue, uint256 liabilityValue) = _getAccountLiquidity(_account, false); 19 | /// @dev not checking for a liquidatable account, just an unhealthy one 20 | return isAccountHealthy(liabilityValue, collateralValue); 21 | } 22 | 23 | /// @notice Checks whether the account is healthy from a LIQUIDATION perspective 24 | function isAccountHealthyLiquidation(address _account) internal view returns (bool) { 25 | (uint256 collateralValue, uint256 liabilityValue) = _getAccountLiquidity(_account, true); 26 | /// @dev checking for a liquidatable account, just an unhealthy one 27 | return isAccountHealthy(liabilityValue, collateralValue) && liabilityValue > 0; 28 | } 29 | 30 | /// @notice Checks whether the account is healthy 31 | function assertAccountIsHealthy(address _account) internal { 32 | assertTrue(isAccountHealthy(_account), "Account is unhealthy for BORROWING"); 33 | } 34 | 35 | /// @notice Checks whether the account is healthy from a LIQUIDATION perspective 36 | function assertAccountIsHealthyLiquidation(address _account) internal { 37 | assertTrue(isAccountHealthyLiquidation(_account), "Account is unhealthy for LIQUIDATION"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/invariants/handlers/interfaces/ILiquidationModuleHandler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | interface ILiquidationModuleHandler { 5 | function liquidate(uint256 repayAssets, uint256 minYielBalance, uint256 i) external; 6 | } 7 | -------------------------------------------------------------------------------- /test/invariants/handlers/modules/BalanceForwarderModuleHandler.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Test Contracts 5 | import {Actor} from "../../utils/Actor.sol"; 6 | import {BaseHandler} from "../../base/BaseHandler.t.sol"; 7 | 8 | // Interfaces 9 | import {IBalanceForwarder} from "../../../../src/EVault/IEVault.sol"; 10 | 11 | /// @title BalanceForwarderModuleHandler 12 | /// @notice Handler test contract for the risk balance forwarder module actions 13 | contract BalanceForwarderModuleHandler is BaseHandler { 14 | /////////////////////////////////////////////////////////////////////////////////////////////// 15 | // STATE VARIABLES // 16 | /////////////////////////////////////////////////////////////////////////////////////////////// 17 | 18 | /////////////////////////////////////////////////////////////////////////////////////////////// 19 | // ACTIONS // 20 | /////////////////////////////////////////////////////////////////////////////////////////////// 21 | 22 | function enableBalanceForwarder() external setup { 23 | bool success; 24 | bytes memory returnData; 25 | 26 | // Get one of the three actors randomly 27 | address target = address(eTST); 28 | 29 | (success, returnData) = 30 | actor.proxy(target, abi.encodeWithSelector(IBalanceForwarder.enableBalanceForwarder.selector)); 31 | 32 | if (success) { 33 | assert(true); 34 | } 35 | } 36 | 37 | function disableBalanceForwarder() external setup { 38 | bool success; 39 | bytes memory returnData; 40 | 41 | // Get one of the three actors randomly 42 | address target = address(eTST); 43 | 44 | (success, returnData) = 45 | actor.proxy(target, abi.encodeWithSelector(IBalanceForwarder.disableBalanceForwarder.selector)); 46 | 47 | if (success) { 48 | assert(true); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/invariants/handlers/modules/RiskManagerModuleHandler.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Test Contracts 5 | import {Actor} from "../../utils/Actor.sol"; 6 | import {BaseHandler} from "../../base/BaseHandler.t.sol"; 7 | 8 | // Interfaces 9 | import {IRiskManager} from "../../../../src/EVault/IEVault.sol"; 10 | 11 | /// @title RiskManagerModuleHandler 12 | /// @notice Handler test contract for the risk manager module actions 13 | contract RiskManagerModuleHandler is BaseHandler { 14 | /////////////////////////////////////////////////////////////////////////////////////////////// 15 | // STATE VARIABLES // 16 | /////////////////////////////////////////////////////////////////////////////////////////////// 17 | 18 | /////////////////////////////////////////////////////////////////////////////////////////////// 19 | // ACTIONS // 20 | /////////////////////////////////////////////////////////////////////////////////////////////// 21 | 22 | function disableController() external setup { 23 | bool success; 24 | bytes memory returnData; 25 | 26 | // Get one of the three actors randomly 27 | address target = address(eTST); 28 | 29 | (success, returnData) = actor.proxy(target, abi.encodeWithSelector(IRiskManager.disableController.selector)); 30 | 31 | if (success) { 32 | assert(true); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/invariants/handlers/simulators/DonationAttackHandler.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Libraries 5 | import {TestERC20} from "../../Setup.t.sol"; 6 | 7 | // Contracts 8 | import {Actor} from "../../utils/Actor.sol"; 9 | import {BaseHandler} from "../../base/BaseHandler.t.sol"; 10 | 11 | /// @title DonationAttackHandler 12 | /// @notice Handler test contract for the DonationAttack actions 13 | contract DonationAttackHandler is BaseHandler { 14 | /////////////////////////////////////////////////////////////////////////////////////////////// 15 | // STATE VARIABLES // 16 | /////////////////////////////////////////////////////////////////////////////////////////////// 17 | 18 | /////////////////////////////////////////////////////////////////////////////////////////////// 19 | // GHOST VARAIBLES // 20 | /////////////////////////////////////////////////////////////////////////////////////////////// 21 | 22 | /////////////////////////////////////////////////////////////////////////////////////////////// 23 | // ACTIONS // 24 | /////////////////////////////////////////////////////////////////////////////////////////////// 25 | 26 | /// @notice This function transfers any amount of assets to a contract in the system 27 | /// @dev Flashloan simulator 28 | function donate(uint256 amount, uint256 j) external { 29 | address vaultAddress = address(eTST); 30 | 31 | TestERC20 _token = TestERC20(_getRandomBaseAsset(j)); 32 | 33 | _token.mint(address(this), amount); 34 | 35 | _token.transfer(vaultAddress, amount); 36 | } 37 | 38 | /////////////////////////////////////////////////////////////////////////////////////////////// 39 | // HELPERS // 40 | /////////////////////////////////////////////////////////////////////////////////////////////// 41 | } 42 | -------------------------------------------------------------------------------- /test/invariants/handlers/simulators/FlashLoanHandler.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {BaseHandler} from "../../base/BaseHandler.t.sol"; 5 | 6 | /// @title FlashLoanHandler 7 | /// @notice Handler test contract for the IRM actions 8 | contract FlashLoanHandler is BaseHandler { 9 | /////////////////////////////////////////////////////////////////////////////////////////////// 10 | // STATE VARIABLES // 11 | /////////////////////////////////////////////////////////////////////////////////////////////// 12 | /////////////////////////////////////////////////////////////////////////////////////////////// 13 | // GHOST VARAIBLES // 14 | /////////////////////////////////////////////////////////////////////////////////////////////// 15 | /////////////////////////////////////////////////////////////////////////////////////////////// 16 | // ACTIONS // 17 | /////////////////////////////////////////////////////////////////////////////////////////////// 18 | 19 | /////////////////////////////////////////////////////////////////////////////////////////////// 20 | // HELPERS // 21 | /////////////////////////////////////////////////////////////////////////////////////////////// 22 | } 23 | -------------------------------------------------------------------------------- /test/invariants/handlers/simulators/IRMHandler.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {BaseHandler} from "../../base/BaseHandler.t.sol"; 5 | 6 | /// @title IRMHandler 7 | /// @notice Handler test contract for the IRM actions 8 | contract IRMHandler is BaseHandler { 9 | /////////////////////////////////////////////////////////////////////////////////////////////// 10 | // STATE VARIABLES // 11 | /////////////////////////////////////////////////////////////////////////////////////////////// 12 | /////////////////////////////////////////////////////////////////////////////////////////////// 13 | // GHOST VARAIBLES // 14 | /////////////////////////////////////////////////////////////////////////////////////////////// 15 | 16 | /////////////////////////////////////////////////////////////////////////////////////////////// 17 | // ACTIONS // 18 | /////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | /////////////////////////////////////////////////////////////////////////////////////////////// 21 | // HELPERS // 22 | /////////////////////////////////////////////////////////////////////////////////////////////// 23 | } 24 | -------------------------------------------------------------------------------- /test/invariants/handlers/simulators/PriceOracleHandler.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {BaseHandler} from "../../base/BaseHandler.t.sol"; 5 | 6 | /// @title PriceOracleHandler 7 | /// @notice Handler test contract for the PriceOracle actions 8 | contract PriceOracleHandler is BaseHandler { 9 | /////////////////////////////////////////////////////////////////////////////////////////////// 10 | // STATE VARIABLES // 11 | /////////////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | /////////////////////////////////////////////////////////////////////////////////////////////// 14 | // GHOST VARAIBLES // 15 | /////////////////////////////////////////////////////////////////////////////////////////////// 16 | 17 | /////////////////////////////////////////////////////////////////////////////////////////////// 18 | // ACTIONS // 19 | /////////////////////////////////////////////////////////////////////////////////////////////// 20 | 21 | /// @notice This function simulates changes in the interest rate model 22 | function setPrice(uint256 i, uint256 price) external { 23 | address baseAsset = _getRandomBaseAsset(i); 24 | 25 | oracle.setPrice(baseAsset, unitOfAccount, price); 26 | } 27 | 28 | /* 29 | /// @notice This function simulates changes in the interest rate model 30 | function setResolvedAsset(uint256 i) external { 31 | address vaultAddress = address(eTST); 32 | 33 | oracle.setResolvedAsset(vaultAddress); 34 | } */ 35 | 36 | /////////////////////////////////////////////////////////////////////////////////////////////// 37 | // HELPERS // 38 | /////////////////////////////////////////////////////////////////////////////////////////////// 39 | } 40 | -------------------------------------------------------------------------------- /test/invariants/helpers/VaultBaseGetters.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.19; 3 | 4 | import {console} from "forge-std/console.sol"; 5 | 6 | /// @title VaultBaseGetters 7 | /// @dev This contract provides getters for the private variables in VaultBase, via storage access 8 | contract VaultBaseGetters { 9 | uint256 internal constant REENTRANCY_LOCK_SLOT = 0; 10 | uint256 internal constant SNAPSHOT_SLOT = 1; 11 | 12 | /// @notice Gets the reentrancy lock 13 | function getReentrancyLock() external view returns (uint256 lock) { 14 | uint256 slot = REENTRANCY_LOCK_SLOT; 15 | 16 | assembly { 17 | lock := sload(slot) 18 | } 19 | } 20 | 21 | /// @notice Gets the snapshot length, using assembly to read from storage 22 | function getSnapshotLength() external view returns (uint256 snapshotLength) { 23 | uint256 slot = SNAPSHOT_SLOT; 24 | 25 | assembly { 26 | snapshotLength := sload(slot) 27 | } 28 | } 29 | 30 | /// @notice Gets the snapshot, using assembly to read bytes from storage 31 | function getSnapshot() external view returns (bytes memory snapshot) { 32 | uint256 slot = SNAPSHOT_SLOT; 33 | 34 | // Declared outside of the assembly block for easier debugging 35 | uint256 length; 36 | uint256 bytesLength; 37 | uint256 slotContent; 38 | uint256 slotData; 39 | 40 | assembly { 41 | // Calculate slot where data starts 42 | mstore(0, slot) 43 | slotData := keccak256(0, 0x20) 44 | 45 | slotContent := sload(slot) 46 | 47 | if gt(slotContent, 0) { 48 | // Load the length of the bytes 49 | bytesLength := sub(slotContent, 0x21) 50 | 51 | // Calculate the number of 32-byte chunks 52 | length := div(add(bytesLength, 0x1f), 0x20) 53 | 54 | // Update the free memory pointer 55 | mstore(0x40, add(snapshot, add(mul(length, 0x20), 0x20))) 56 | 57 | // Store the length of the bytes in memory 58 | let pointer := snapshot 59 | mstore(pointer, bytesLength) 60 | 61 | for { let i := 0 } lt(i, length) { i := add(i, 1) } { 62 | // Calculate the next slot to read 63 | let dataSlot := add(slotData, i) 64 | // Read the data from the slot 65 | let data := sload(dataSlot) 66 | 67 | // Calculate the next memory pointer & store the data 68 | pointer := add(pointer, 0x20) 69 | mstore(pointer, data) 70 | } 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test/invariants/hooks/HookAggregator.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Hook Contracts 5 | import {VaultBeforeAfterHooks} from "./VaultBeforeAfterHooks.t.sol"; 6 | import {BorrowingBeforeAfterHooks} from "./BorrowingBeforeAfterHooks.t.sol"; 7 | 8 | /// @title HookAggregator 9 | /// @notice Helper contract to aggregate all before / after hook contracts, inherited on each handler 10 | abstract contract HookAggregator is VaultBeforeAfterHooks, BorrowingBeforeAfterHooks { 11 | /// @notice Modular hook selector, per module 12 | function _before() internal { 13 | _vaultHooksBefore(); 14 | _borrowingHooksBefore(); 15 | } 16 | 17 | /// @notice Modular hook selector, per module 18 | function _after() internal { 19 | _vaultHooksAfter(); 20 | _borrowingHooksAfter(); 21 | 22 | // Postconditions 23 | _checkPostConditions(); 24 | } 25 | 26 | /// @notice Postconditions for the handlers 27 | function _checkPostConditions() internal { 28 | // Vault 29 | assert_VM_INVARIANT_B(); 30 | assert_LM_INVARIANT_B(); 31 | 32 | // Borrowing 33 | assert_I_INVARIANT_E(); 34 | assert_BM_INVARIANT_H(); 35 | assert_BM_INVARIANT_I(); 36 | 37 | // Liquidation 38 | assert_LM_INVARIANT_C(); 39 | assert_LM_INVARIANT_D(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/invariants/invariants/BaseInvariants.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Contracts 5 | import {HandlerAggregator} from "../HandlerAggregator.t.sol"; 6 | 7 | // Types 8 | import {Snapshot, Assets} from "../../../src/EVault/shared/types/Types.sol"; 9 | 10 | /// @title BaseInvariants 11 | /// @notice Implements Invariants for the protocol 12 | /// @dev Inherits HandlerAggregator to check actions in assertion testing mode 13 | abstract contract BaseInvariants is HandlerAggregator { 14 | function assert_BASE_INVARIANT_A() internal { 15 | assertEq(eTST.getReentrancyLock(), false, BASE_INVARIANT_A); 16 | } 17 | 18 | function assert_BASE_INVARIANT_B() internal { 19 | Snapshot memory _snapshot = eTST.getSnapshot(); 20 | assertEq(_snapshot.stamp, 1, BASE_INVARIANT_B); 21 | assertEq(Assets.unwrap(_snapshot.cash), 0, BASE_INVARIANT_B); 22 | assertEq(Assets.unwrap(_snapshot.borrows), 0, BASE_INVARIANT_B); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/invariants/invariants/InterestInvariants.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Contracts 5 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "../../../src/EVault/shared/Constants.sol"; 7 | 8 | // Base Contracts 9 | import {HandlerAggregator} from "../HandlerAggregator.t.sol"; 10 | 11 | /// @title InterestInvariants 12 | /// @notice Implements Invariants related to the interest 13 | /// @dev Inherits HandlerAggregator for checking actions in assertion testing mode 14 | abstract contract InterestInvariants is HandlerAggregator { 15 | function assert_I_INVARIANT_A() internal { 16 | (uint256 min, uint256 max) = protocolConfig.interestFeeRange(address(eTST)); 17 | assertLe(eTST.interestFee(), max, I_INVARIANT_A); 18 | assertGe(eTST.interestFee(), min, I_INVARIANT_A); 19 | } 20 | 21 | function assert_I_INVARIANT_B() internal { 22 | assertLe(eTST.getLastInterestAccumulatorUpdate(), block.timestamp, BASE_INVARIANT_B); 23 | } 24 | 25 | function assert_I_INVARIANT_D() internal { 26 | assertLe(eTST.interestRate(), MAX_ALLOWED_INTEREST_RATE, I_INVARIANT_D); 27 | } 28 | 29 | ////////////////////////////////////////////////////////////////////////////////////////////// 30 | // HELPERS // 31 | ////////////////////////////////////////////////////////////////////////////////////////////// 32 | } 33 | -------------------------------------------------------------------------------- /test/invariants/invariants/LiquidationModuleInvariants.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Base Contracts 5 | import {HandlerAggregator} from "../HandlerAggregator.t.sol"; 6 | 7 | /// @title LiquidationModuleInvariants 8 | /// @notice Implements Invariants for the liquidation module 9 | /// @dev Inherits HandlerAggregator for checking actions in assertion testing mode 10 | abstract contract LiquidationModuleInvariants is HandlerAggregator {} 11 | -------------------------------------------------------------------------------- /test/invariants/invariants/TokenModuleInvariants.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | import {HandlerAggregator} from "../HandlerAggregator.t.sol"; 5 | 6 | import "forge-std/console.sol"; 7 | 8 | // Contracts 9 | 10 | /// @title TokenModuleInvariants 11 | /// @notice Implements Invariants for the ERC20 functionality of the vault 12 | /// @dev Inherits HandlerAggregator to check actions in assertion testing mode 13 | abstract contract TokenModuleInvariants is HandlerAggregator { 14 | function assert_TM_INVARIANT_A() internal { 15 | uint256 extrabalance = eTST.balanceOf(eTST.feeReceiver()) + eTST.balanceOf(eTST.protocolFeeReceiver()); 16 | assertApproxEqAbs( 17 | eTST.totalSupply(), 18 | ghost_sumSharesBalances + eTST.accumulatedFees() + extrabalance, 19 | NUMBER_OF_ACTORS, 20 | TM_INVARIANT_A 21 | ); 22 | } 23 | 24 | function assert_TM_INVARIANT_B(address _account) internal { 25 | assertEq(eTST.balanceOf(_account), ghost_sumSharesBalancesPerUser[_account], TM_INVARIANT_B); 26 | } 27 | 28 | function assert_TM_INVARIANT_C(uint256 _sumBalances) internal { 29 | uint256 extrabalance = eTST.balanceOf(eTST.feeReceiver()) + eTST.balanceOf(eTST.protocolFeeReceiver()); 30 | assertEq(eTST.totalSupply(), _sumBalances + eTST.accumulatedFees() + extrabalance, TM_INVARIANT_C); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/invariants/utils/Actor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | // Interfaces 5 | import {IERC20} from "forge-std/interfaces/IERC20.sol"; 6 | 7 | /// @title Actor 8 | /// @notice Proxy contract for invariant suite actors to avoid Tester calling contracts 9 | /// @dev This expands the flexibility of the invariant suite 10 | contract Actor { 11 | /// @notice list of tokens to approve 12 | address[] internal tokens; 13 | /// @notice list of callers to approve tokens to 14 | address[] internal callers; 15 | 16 | constructor(address[] memory _tokens, address[] memory _callers) payable { 17 | tokens = _tokens; 18 | callers = _callers; 19 | for (uint256 i = 0; i < tokens.length; i++) { 20 | IERC20(tokens[i]).approve(callers[i], type(uint256).max); 21 | } 22 | } 23 | 24 | /// @notice Helper function to proxy a call to a target contract, used to avoid Tester calling contracts 25 | function proxy(address _target, bytes memory _calldata) public returns (bool success, bytes memory returnData) { 26 | (success, returnData) = address(_target).call(_calldata); 27 | } 28 | 29 | /// @notice Helper function to proxy a call and value to a target contract, used to avoid Tester calling contracts 30 | function proxy(address _target, bytes memory _calldata, uint256 value) 31 | public 32 | returns (bool success, bytes memory returnData) 33 | { 34 | (success, returnData) = address(_target).call{value: value}(_calldata); 35 | } 36 | 37 | receive() external payable {} 38 | } 39 | -------------------------------------------------------------------------------- /test/invariants/utils/PropertiesConstants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | abstract contract PropertiesConstants { 5 | // Constant echidna addresses 6 | address constant USER1 = address(0x10000); 7 | address constant USER2 = address(0x20000); 8 | address constant USER3 = address(0x30000); 9 | uint256 constant INITIAL_BALANCE = 1000e18; 10 | } 11 | -------------------------------------------------------------------------------- /test/mocks/IRMFailed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/InterestRateModels/IIRM.sol"; 6 | 7 | contract IRMFailed is IIRM { 8 | function computeInterestRate(address, uint256, uint256) external pure override returns (uint256) { 9 | revert(); 10 | } 11 | 12 | function computeInterestRateView(address, uint256, uint256) external pure override returns (uint256) { 13 | revert(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/mocks/IRMMax.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/InterestRateModels/IIRM.sol"; 6 | import "../../src/EVault/shared/Constants.sol"; 7 | 8 | contract IRMMax is IIRM { 9 | function computeInterestRate(address vault, uint256, uint256) external view override returns (uint256) { 10 | if (msg.sender != vault) revert E_IRMUpdateUnauthorized(); 11 | return MAX_ALLOWED_INTEREST_RATE; 12 | } 13 | 14 | function computeInterestRateView(address, uint256, uint256) external pure override returns (uint256) { 15 | return MAX_ALLOWED_INTEREST_RATE; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/mocks/IRMOverBound.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/InterestRateModels/IIRM.sol"; 6 | import "../../src/EVault/shared/Constants.sol"; 7 | 8 | contract IRMOverBound is IIRM { 9 | function computeInterestRate(address vault, uint256, uint256) external view override returns (uint256) { 10 | if (msg.sender != vault) revert E_IRMUpdateUnauthorized(); 11 | return MAX_ALLOWED_INTEREST_RATE + 100; 12 | } 13 | 14 | function computeInterestRateView(address, uint256, uint256) external pure override returns (uint256) { 15 | return MAX_ALLOWED_INTEREST_RATE + 100; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/mocks/IRMTestDefault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/InterestRateModels/IRMLinearKink.sol"; 6 | 7 | contract IRMTestDefault is IRMLinearKink { 8 | constructor() 9 | // Base=0% APY, Kink(50%)=10% APY Max=300% APY 10 | IRMLinearKink(0, 1406417851, 19050045013, 2147483648) 11 | {} 12 | } 13 | -------------------------------------------------------------------------------- /test/mocks/IRMTestFixed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/InterestRateModels/IIRM.sol"; 6 | 7 | contract IRMTestFixed is IIRM { 8 | constructor() {} 9 | 10 | function computeInterestRate(address vault, uint256, uint256) public view returns (uint256) { 11 | if (msg.sender != vault) revert E_IRMUpdateUnauthorized(); 12 | return uint256(1e27 * 0.1) / (86400 * 365); // not SECONDS_PER_YEAR to avoid breaking tests 13 | } 14 | 15 | function computeInterestRateView(address vault, uint256 cash, uint256 borrows) external view returns (uint256) { 16 | return computeInterestRate(vault, cash, borrows); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/mocks/IRMTestLinear.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/InterestRateModels/IIRM.sol"; 6 | import "../../src/EVault/shared/Constants.sol"; 7 | 8 | contract IRMTestLinear is IIRM { 9 | uint256 internal constant MAX_IR = uint256(1e27 * 0.1) / SECONDS_PER_YEAR; 10 | 11 | function computeInterestRate(address vault, uint256 cash, uint256 borrows) public view returns (uint256) { 12 | if (msg.sender != vault) revert E_IRMUpdateUnauthorized(); 13 | uint256 totalAssets = cash + borrows; 14 | 15 | uint32 utilisation = totalAssets == 0 16 | ? 0 // empty pool arbitrarily given utilisation of 0 17 | : uint32(borrows * type(uint32).max / totalAssets); 18 | 19 | return MAX_IR * utilisation / type(uint32).max; 20 | } 21 | 22 | function computeInterestRateView(address vault, uint256 cash, uint256 borrows) external view returns (uint256) { 23 | return computeInterestRate(vault, cash, borrows); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/mocks/IRMTestZero.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/InterestRateModels/IIRM.sol"; 6 | 7 | contract IRMTestZero is IIRM { 8 | constructor() {} 9 | 10 | function computeInterestRate(address vault, uint256, uint256) public view returns (uint256) { 11 | if (msg.sender != vault) revert E_IRMUpdateUnauthorized(); 12 | return 0; 13 | } 14 | 15 | function computeInterestRateView(address vault, uint256 cash, uint256 borrows) external view returns (uint256) { 16 | return computeInterestRate(vault, cash, borrows); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/mocks/MockBalanceTracker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.23; 4 | 5 | contract MockBalanceTracker { 6 | uint256 public numCalls; 7 | mapping(address => mapping(uint256 => mapping(bool => uint256))) public calls; 8 | mapping(address => uint256) public balance; 9 | 10 | struct ReentrantCall { 11 | address to; 12 | bytes data; 13 | } 14 | 15 | ReentrantCall reentrantCall; 16 | 17 | function balanceTrackerHook(address account, uint256 newAccountBalance, bool forfeitRecentReward) external { 18 | calls[account][newAccountBalance][forfeitRecentReward]++; 19 | balance[account] = newAccountBalance; 20 | numCalls++; 21 | 22 | ReentrantCall memory _reentrantCall = reentrantCall; 23 | if (_reentrantCall.to == address(0)) return; 24 | 25 | reentrantCall = ReentrantCall(address(0), ""); 26 | 27 | (bool success, bytes memory result) = address(_reentrantCall.to).call(_reentrantCall.data); 28 | if (success) return; 29 | if (result.length == 0) revert(); 30 | assembly { 31 | revert(add(32, result), mload(result)) 32 | } 33 | } 34 | 35 | function setReentrantCall(address to, bytes memory data) external { 36 | reentrantCall.to = to; 37 | reentrantCall.data = data; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/mocks/MockDecimals.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.23; 4 | 5 | contract MockDecimals { 6 | uint8 public decimals; 7 | 8 | constructor(uint8 decimals_) { 9 | decimals = decimals_; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/mocks/MockEVault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract MockEVault { 6 | constructor(address factory_, address evc_) {} 7 | 8 | function initialize(address) external {} 9 | 10 | function implementation() external pure returns (string memory) { 11 | return "TRANSPARENT"; 12 | } 13 | 14 | function UNPACK() internal pure returns (address vaultAsset) { 15 | assembly { 16 | vaultAsset := shr(96, calldataload(sub(calldatasize(), 20))) 17 | } 18 | } 19 | 20 | function arbitraryFunction(string calldata arg) external view returns (string memory, address, address) { 21 | (address vaultAsset) = UNPACK(); 22 | return (arg, msg.sender, vaultAsset); 23 | } 24 | 25 | function payMe() external payable {} 26 | } 27 | -------------------------------------------------------------------------------- /test/mocks/MockMinimalStatusCheck.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract MockMinimalStatusCheck { 6 | bool public shouldFail; 7 | 8 | function setShouldFail(bool shouldPass_) external { 9 | shouldFail = shouldPass_; 10 | } 11 | 12 | function checkAccountStatus(address, address[] calldata) external view returns (bytes4 magicValue) { 13 | require(!shouldFail, "MockMinimalStatusCheck: account status check failed"); 14 | return this.checkAccountStatus.selector; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/mocks/MockPriceOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/EVault/IEVault.sol"; 6 | 7 | contract MockPriceOracle { 8 | error PO_BaseUnsupported(); 9 | error PO_QuoteUnsupported(); 10 | error PO_Overflow(); 11 | error PO_NoPath(); 12 | 13 | mapping(address base => mapping(address quote => uint256)) price; 14 | mapping(address base => mapping(address quote => Prices)) prices; 15 | 16 | struct Prices { 17 | bool set; 18 | uint256 bid; 19 | uint256 ask; 20 | } 21 | 22 | function name() external pure returns (string memory) { 23 | return "MockPriceOracle"; 24 | } 25 | 26 | function getQuote(uint256 amount, address base, address quote) public view returns (uint256 out) { 27 | return calculateQuote(base, amount, price[resolveUnderlying(base)][quote]); 28 | } 29 | 30 | function getQuotes(uint256 amount, address base, address quote) 31 | external 32 | view 33 | returns (uint256 bidOut, uint256 askOut) 34 | { 35 | if (prices[resolveUnderlying(base)][quote].set) { 36 | return ( 37 | calculateQuote(base, amount, prices[resolveUnderlying(base)][quote].bid), 38 | calculateQuote(base, amount, prices[resolveUnderlying(base)][quote].ask) 39 | ); 40 | } 41 | 42 | bidOut = askOut = getQuote(amount, base, quote); 43 | } 44 | 45 | ///// Mock functions 46 | 47 | function setPrice(address base, address quote, uint256 newPrice) external { 48 | price[resolveUnderlying(base)][quote] = newPrice; 49 | } 50 | 51 | function setPrices(address base, address quote, uint256 newBid, uint256 newAsk) external { 52 | prices[resolveUnderlying(base)][quote] = Prices({set: true, bid: newBid, ask: newAsk}); 53 | } 54 | 55 | function calculateQuote(address base, uint256 amount, uint256 p) internal view returns (uint256) { 56 | // While base is a vault (for the purpose of the mock, if it implements asset()), then call 57 | // convertToAssets() to price its shares. This is similar to how EulerRouter implements 58 | // "resolved" vaults. 59 | 60 | while (base.code.length > 0) { 61 | (bool success, bytes memory data) = base.staticcall(abi.encodeCall(IERC4626.asset, ())); 62 | if (!success) break; 63 | 64 | (address asset) = abi.decode(data, (address)); 65 | amount = IEVault(base).convertToAssets(amount); 66 | base = asset; 67 | } 68 | 69 | return amount * p / 1e18; 70 | } 71 | 72 | function resolveUnderlying(address asset) internal view returns (address) { 73 | if (asset.code.length > 0) { 74 | (bool success, bytes memory data) = asset.staticcall(abi.encodeCall(IERC4626.asset, ())); 75 | if (success) return abi.decode(data, (address)); 76 | } 77 | 78 | return asset; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/mocks/MockWrongEVC.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract MockWrongEVC { 6 | function EVC() external pure returns (address) { 7 | return address(420); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/mocks/Permit2ECDSASigner.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {PermitHash} from "permit2/src/libraries/PermitHash.sol"; 6 | import {IEIP712} from "permit2/src/interfaces/IEIP712.sol"; 7 | import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; 8 | 9 | contract Permit2ECDSASigner is Test { 10 | address private immutable permit2; 11 | 12 | constructor(address _permit2) { 13 | permit2 = _permit2; 14 | } 15 | 16 | function signPermitSingle(uint256 privateKey, IAllowanceTransfer.PermitSingle memory permitSingle) 17 | external 18 | view 19 | returns (bytes memory signature) 20 | { 21 | bytes32 structHash = keccak256( 22 | abi.encode( 23 | PermitHash._PERMIT_SINGLE_TYPEHASH, 24 | keccak256(abi.encode(PermitHash._PERMIT_DETAILS_TYPEHASH, permitSingle.details)), 25 | permitSingle.spender, 26 | permitSingle.sigDeadline 27 | ) 28 | ); 29 | (uint8 v, bytes32 r, bytes32 s) = vm.sign( 30 | privateKey, keccak256(abi.encodePacked("\x19\x01", IEIP712(permit2).DOMAIN_SEPARATOR(), structHash)) 31 | ); 32 | signature = abi.encodePacked(r, s, v); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/mocks/ReentrancyAttack.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface IVault { 6 | function initialize(address creator) external; 7 | } 8 | 9 | interface IGenericFactory { 10 | function createProxy(address desiredImplementation, bool upgradeable, bytes memory trailingData) 11 | external 12 | returns (address); 13 | } 14 | 15 | contract ReentrancyAttack is IVault { 16 | address immutable factory; 17 | address immutable asset; 18 | 19 | constructor(address _factory, address _asset) { 20 | factory = _factory; 21 | asset = _asset; 22 | } 23 | 24 | function initialize(address) external { 25 | IGenericFactory(factory).createProxy(address(0), true, abi.encodePacked(asset, address(this))); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # generates lcov.info 4 | forge coverage --report lcov --no-match-test testFork 5 | 6 | if ! command -v lcov &>/dev/null; then 7 | echo "lcov is not installed. Installing..." 8 | # check if its macos or linux. 9 | if [ "$(uname)" == "Darwin" ]; then 10 | brew install lcov 11 | else 12 | sudo apt-get install lcov 13 | fi 14 | fi 15 | 16 | lcov --version 17 | 18 | # forge does not instrument libraries https://github.com/foundry-rs/foundry/issues/4854 19 | EXCLUDE="*test* *mock* *node_modules* $(grep -r 'library' contracts -l)" 20 | lcov --rc branch_coverage=1 \ 21 | --output-file forge-pruned-lcov.info \ 22 | --ignore-errors inconsistent \ 23 | --remove lcov.info $EXCLUDE 24 | 25 | if [ "$CI" != "true" ]; then 26 | genhtml --rc branch_coverage=1 \ 27 | --ignore-errors category \ 28 | --ignore-errors inconsistent \ 29 | --output-directory coverage forge-pruned-lcov.info \ 30 | && open coverage/index.html 31 | fi 32 | -------------------------------------------------------------------------------- /test/scripts/echidna-assert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echidna test/invariants/Tester.t.sol --test-mode assertion --contract Tester --config ./test/invariants/_config/echidna_config.yaml --corpus-dir ./test/invariants/_corpus/echidna/default/_data/corpus -------------------------------------------------------------------------------- /test/scripts/echidna.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echidna test/invariants/Tester.t.sol --contract Tester --config ./test/invariants/_config/echidna_config.yaml --corpus-dir ./test/invariants/_corpus/echidna/default/_data/corpus -------------------------------------------------------------------------------- /test/scripts/medusa.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | medusa fuzz -------------------------------------------------------------------------------- /test/unit/esr/lib/ESRTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.20; 3 | 4 | import "forge-std/Test.sol"; 5 | import {EthereumVaultConnector as EVC} from "ethereum-vault-connector/EthereumVaultConnector.sol"; 6 | import {EulerSavingsRate} from "../../../../src/Synths/EulerSavingsRate.sol"; 7 | import {MockToken} from "./MockToken.sol"; 8 | 9 | contract ESRTest is Test { 10 | EVC public evc; 11 | EulerSavingsRate public esr; 12 | MockToken public asset; 13 | 14 | address public distributor = makeAddr("distributor"); 15 | address public user = makeAddr("user"); 16 | 17 | string public constant NAME = "Euler Savings Rate"; 18 | string public constant SYMBOL = "ESR"; 19 | 20 | function setUp() public virtual { 21 | asset = new MockToken(); 22 | evc = new EVC(); 23 | esr = new EulerSavingsRate(address(evc), address(asset), NAME, SYMBOL); 24 | 25 | // Set a non zero timestamp 26 | vm.warp(420); 27 | } 28 | 29 | // utils 30 | function doDeposit(address from, uint256 amount) public { 31 | asset.mint(from, amount); 32 | 33 | vm.startPrank(from); 34 | asset.approve(address(esr), amount); 35 | esr.deposit(amount, from); 36 | vm.stopPrank(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/unit/esr/lib/MockToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.20; 3 | 4 | import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract MockToken is ERC20 { 7 | constructor() ERC20("Mock Token", "MOCK") {} 8 | 9 | function mint(address to, uint256 amount) public { 10 | _mint(to, amount); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/unit/esvault/ESVault.allocate.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {ESVaultTestBase, ESynth} from "./ESVaultTestBase.t.sol"; 6 | import {MockHook} from "../evault/EVaultTestBase.t.sol"; 7 | 8 | contract ESVaultTestAllocate is ESVaultTestBase { 9 | function setUp() public override { 10 | super.setUp(); 11 | 12 | assetTSTAsSynth.setCapacity(address(this), 10000e18); 13 | } 14 | 15 | function test_allocate_from_non_synth() public { 16 | vm.expectRevert(MockHook.E_OnlyAssetCanDeposit.selector); 17 | eTST.deposit(100, address(this)); 18 | 19 | vm.expectRevert(MockHook.E_OperationDisabled.selector); 20 | eTST.mint(100, address(this)); 21 | 22 | vm.expectRevert(MockHook.E_OperationDisabled.selector); 23 | eTST.skim(100, address(this)); 24 | 25 | assertEq(eTST.maxDeposit(address(this)), type(uint112).max - eTST.cash()); 26 | 27 | assertEq(eTST.maxMint(address(this)), type(uint112).max - eTST.totalSupply()); 28 | 29 | assertEq(eTST.maxRedeem(address(this)), eTST.balanceOf(address(this))); 30 | } 31 | 32 | function test_allocate_from_synth() public { 33 | assetTSTAsSynth.mint(address(assetTSTAsSynth), 100); 34 | assetTSTAsSynth.allocate(address(eTST), 100); 35 | 36 | assertEq(assetTSTAsSynth.isIgnoredForTotalSupply(address(eTST)), true); 37 | assertEq(assetTST.balanceOf(address(eTST)), 100); 38 | assertEq(eTST.balanceOf(address(assetTST)), 100); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/unit/esvault/ESVault.hookedOps.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {ESVaultTestBase, ESynth} from "./ESVaultTestBase.t.sol"; 6 | import {Errors} from "../../../src/EVault/shared/Errors.sol"; 7 | 8 | contract ESVaultTestHookedOps is ESVaultTestBase { 9 | function setUp() public override { 10 | super.setUp(); 11 | } 12 | 13 | function test_hooked_ops_after_init() public view { 14 | (address hookTarget, uint32 hookedOps) = eTST.hookConfig(); 15 | assertEq(hookTarget, SYNTH_VAULT_HOOK_TARGET); 16 | assertEq(hookedOps, SYNTH_VAULT_HOOKED_OPS); 17 | } 18 | 19 | function test_hooked_ops_disabled_if_no_hook_target() public { 20 | (, uint32 hookedOps) = eTST.hookConfig(); 21 | eTST.setHookConfig(address(0), hookedOps); 22 | 23 | vm.expectRevert(Errors.E_OperationDisabled.selector); 24 | eTST.deposit(100, address(this)); 25 | 26 | vm.expectRevert(Errors.E_OperationDisabled.selector); 27 | eTST.mint(100, address(this)); 28 | 29 | vm.expectRevert(Errors.E_OperationDisabled.selector); 30 | eTST.redeem(100, address(this), address(this)); 31 | 32 | vm.expectRevert(Errors.E_OperationDisabled.selector); 33 | eTST.skim(100, address(this)); 34 | 35 | vm.expectRevert(Errors.E_OperationDisabled.selector); 36 | eTST.repayWithShares(100, address(this)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/unit/esvault/ESVault.interestFee.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {ESVaultTestBase} from "./ESVaultTestBase.t.sol"; 6 | import {Errors} from "../../../src/EVault/shared/Errors.sol"; 7 | 8 | contract ESVaultTestInterestFee is ESVaultTestBase { 9 | function setUp() public override { 10 | super.setUp(); 11 | } 12 | 13 | function test_interest_fee() public view { 14 | uint256 interestFee = eTST.interestFee(); 15 | assertEq(interestFee, 1e4); 16 | } 17 | 18 | function test_set_interest_fee() public { 19 | // This protection is not currently implemented. A governor can change the interestFee 20 | // of a synth vault to a non-100% value. Don't do that. 21 | 22 | //vm.expectRevert(Errors.E_OperationDisabled.selector); 23 | //eTST.setInterestFee(0.5e4); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/unit/esvault/ESVaultTestBase.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {EVaultTestBase} from "../evault/EVaultTestBase.t.sol"; 6 | import {IEVault, IERC20} from "../../../src/EVault/IEVault.sol"; 7 | import {IRMTestDefault} from "../../mocks/IRMTestDefault.sol"; 8 | import {ESynth} from "../../../src/Synths/ESynth.sol"; 9 | import {TestERC20} from "../../mocks/TestERC20.sol"; 10 | 11 | contract ESVaultTestBase is EVaultTestBase { 12 | ESynth assetTSTAsSynth; 13 | ESynth assetTST2AsSynth; 14 | 15 | function setUp() public virtual override { 16 | super.setUp(); 17 | 18 | assetTSTAsSynth = ESynth(address(new ESynth(address(evc), "Test Synth", "TST"))); 19 | assetTST = TestERC20(address(assetTSTAsSynth)); 20 | assetTST2AsSynth = ESynth(address(new ESynth(address(evc), "Test Synth 2", "TST2"))); 21 | assetTST2 = TestERC20(address(assetTST2AsSynth)); 22 | 23 | eTST = createSynthEVault(address(assetTST)); 24 | 25 | // Set the capacity for the vault on the synth 26 | // assetTSTAsSynth.setCapacity(address(eTST), type(uint128).max); 27 | 28 | eTST2 = createSynthEVault(address(assetTST2)); 29 | 30 | // Set the capacity for the vault on the synth 31 | // assetTST2AsSynth.setCapacity(address(eTST2), type(uint128).max); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/unit/esynth/lib/ESynthTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {EVaultTestBase} from "../../evault/EVaultTestBase.t.sol"; 6 | import {IEVault, IERC20} from "../../../../src/EVault/IEVault.sol"; 7 | import {IRMTestDefault} from "../../../mocks/IRMTestDefault.sol"; 8 | import {ESynth} from "../../../../src/Synths/ESynth.sol"; 9 | import {TestERC20} from "../../../mocks/TestERC20.sol"; 10 | 11 | contract ESynthTest is EVaultTestBase { 12 | ESynth esynth; 13 | address user1; 14 | address user2; 15 | 16 | function setUp() public virtual override { 17 | super.setUp(); 18 | 19 | user1 = vm.addr(1001); 20 | user2 = vm.addr(1002); 21 | 22 | esynth = ESynth(address(new ESynth(address(evc), "Test Synth", "TST"))); 23 | assetTST = TestERC20(address(esynth)); 24 | 25 | eTST = createSynthEVault(address(assetTST)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/unit/evault/Dispatch.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./EVaultTestBase.t.sol"; 6 | import {Errors} from "../../../src/EVault/shared/Errors.sol"; 7 | import {EVault} from "../../../src/EVault/EVault.sol"; 8 | 9 | contract DispatchTest is EVaultTestBase { 10 | function test_Dispatch_moduleGetters() public view { 11 | assertEq(eTST.MODULE_INITIALIZE(), initializeModule); 12 | assertEq(eTST.MODULE_TOKEN(), tokenModule); 13 | assertEq(eTST.MODULE_VAULT(), vaultModule); 14 | assertEq(eTST.MODULE_BORROWING(), borrowingModule); 15 | assertEq(eTST.MODULE_LIQUIDATION(), liquidationModule); 16 | assertEq(eTST.MODULE_RISKMANAGER(), riskManagerModule); 17 | assertEq(eTST.MODULE_BALANCE_FORWARDER(), balanceForwarderModule); 18 | assertEq(eTST.MODULE_GOVERNANCE(), governanceModule); 19 | } 20 | 21 | function test_Dispatch_RevertsWhen_callViewDelegateDirectly() public { 22 | vm.expectRevert(Errors.E_Unauthorized.selector); 23 | EVault(address(eTST)).viewDelegate(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/unit/evault/POC.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {EVaultTestBase} from "./EVaultTestBase.t.sol"; 6 | import "../../../src/EVault/shared/types/Types.sol"; 7 | import "../../../src/EVault/shared/Constants.sol"; 8 | 9 | contract POC_Test is EVaultTestBase { 10 | using TypesLib for uint256; 11 | 12 | function setUp() public override { 13 | // There are 2 vaults deployed with bare minimum configuration: 14 | // - eTST vault using assetTST as the underlying 15 | // - eTST2 vault using assetTST2 as the underlying 16 | 17 | // Both vaults use the same MockPriceOracle and unit of account. 18 | // Both vaults are configured to use IRMTestDefault interest rate model. 19 | // Both vaults are configured to use 0.2e4 max liquidation discount. 20 | // Neither price oracles for the assets nor the LTVs are set. 21 | super.setUp(); 22 | 23 | // In order to further configure the vaults, refer to the Governance module functions. 24 | } 25 | 26 | function test_POC() external {} 27 | } 28 | -------------------------------------------------------------------------------- /test/unit/evault/modules/Governance/convertFees.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../EVaultTestBase.t.sol"; 6 | 7 | contract Governance_ConvertFees is EVaultTestBase { 8 | using TypesLib for uint256; 9 | 10 | address depositor; 11 | address borrower; 12 | 13 | function setUp() public override { 14 | super.setUp(); 15 | 16 | depositor = makeAddr("depositor"); 17 | borrower = makeAddr("borrower"); 18 | 19 | // Setup 20 | 21 | oracle.setPrice(address(assetTST), unitOfAccount, 1e18); 22 | oracle.setPrice(address(assetTST2), unitOfAccount, 1e18); 23 | 24 | eTST.setLTV(address(eTST2), 0.9e4, 0.9e4, 0); 25 | 26 | // Depositor 27 | 28 | startHoax(depositor); 29 | 30 | assetTST.mint(depositor, type(uint256).max); 31 | assetTST.approve(address(eTST), type(uint256).max); 32 | eTST.deposit(100e18, depositor); 33 | 34 | // Borrower 35 | 36 | startHoax(borrower); 37 | 38 | assetTST2.mint(borrower, type(uint256).max); 39 | assetTST2.approve(address(eTST2), type(uint256).max); 40 | eTST2.deposit(10e18, borrower); 41 | 42 | evc.enableCollateral(borrower, address(eTST2)); 43 | evc.enableController(borrower, address(eTST)); 44 | 45 | eTST.borrow(5e18, borrower); 46 | } 47 | 48 | function test_Governance_convertFees() public { 49 | skip(1000 days); 50 | 51 | uint256 fees = eTST.accumulatedFees(); 52 | uint256 totalSupplyBefore = eTST.totalSupply(); 53 | 54 | eTST.convertFees(); 55 | 56 | assertEq(eTST.totalSupply(), totalSupplyBefore); 57 | assertEq(eTST.balanceOf(feeReceiver) + eTST.balanceOf(protocolFeeReceiver), fees); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/unit/evault/modules/Governance/interestRates.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../EVaultTestBase.t.sol"; 6 | 7 | contract Governance_InterestRates is EVaultTestBase { 8 | using TypesLib for uint256; 9 | 10 | address depositor; 11 | address borrower; 12 | 13 | function setUp() public override { 14 | super.setUp(); 15 | 16 | depositor = makeAddr("depositor"); 17 | borrower = makeAddr("borrower"); 18 | 19 | // Setup 20 | 21 | oracle.setPrice(address(assetTST), unitOfAccount, 1e18); 22 | oracle.setPrice(address(assetTST2), unitOfAccount, 1e18); 23 | 24 | eTST.setLTV(address(eTST2), 0.9e4, 0.9e4, 0); 25 | 26 | // Depositor 27 | 28 | startHoax(depositor); 29 | 30 | assetTST.mint(depositor, type(uint256).max); 31 | assetTST.approve(address(eTST), type(uint256).max); 32 | eTST.deposit(100e18, depositor); 33 | 34 | // Borrower 35 | 36 | startHoax(borrower); 37 | 38 | assetTST2.mint(borrower, type(uint256).max); 39 | assetTST2.approve(address(eTST2), type(uint256).max); 40 | eTST2.deposit(10e18, borrower); 41 | 42 | evc.enableCollateral(borrower, address(eTST2)); 43 | evc.enableController(borrower, address(eTST)); 44 | 45 | eTST.borrow(5e18, borrower); 46 | } 47 | 48 | function test_Governance_setInterestRateModel_setAddressZero() public { 49 | assertEq(eTST.totalAssets(), 100e18); 50 | 51 | skip(1 days); 52 | 53 | uint256 beforePause = eTST.totalAssets(); 54 | 55 | // some interest accrued 56 | assertGt(beforePause, 100e18); 57 | 58 | vm.stopPrank(); 59 | address previousIRM = eTST.interestRateModel(); 60 | eTST.setInterestRateModel(address(0)); 61 | 62 | // the previous interest accrued is recorded in the accumulator 63 | assertEq(beforePause, eTST.totalAssets()); 64 | 65 | skip(10 days); 66 | 67 | // no change 68 | assertEq(beforePause, eTST.totalAssets()); 69 | 70 | // set the previous IRM back 71 | eTST.setInterestRateModel(previousIRM); 72 | // no change yet 73 | assertEq(beforePause, eTST.totalAssets()); 74 | 75 | skip(1); 76 | 77 | // interest starts accruing again 78 | assertGt(eTST.totalAssets(), beforePause); 79 | assertApproxEqRel(eTST.totalAssets(), beforePause, 0.0000000001e18); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/unit/evault/modules/Initialize/errors.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../EVaultTestBase.t.sol"; 6 | import {Errors} from "../../../../../src/EVault/shared/Errors.sol"; 7 | import {Events} from "../../../../../src/EVault/shared/Events.sol"; 8 | import {IComponent} from "../../../../../src/GenericFactory/GenericFactory.sol"; 9 | import {MetaProxyDeployer} from "../../../../../src/GenericFactory/MetaProxyDeployer.sol"; 10 | 11 | contract InitializeTests is EVaultTestBase, MetaProxyDeployer { 12 | function test_cant_reinitialize() public { 13 | // On vault 14 | vm.expectRevert(Errors.E_Initialized.selector); 15 | eTST.initialize(msg.sender); 16 | 17 | // Direct on implementation module 18 | vm.expectRevert(Errors.E_Initialized.selector); 19 | IEVault(initializeModule).initialize(msg.sender); 20 | } 21 | 22 | function test_trailing_metadata_check(bytes memory input) public { 23 | vm.assume(input.length != PROXY_METADATA_LENGTH); 24 | bytes memory trailingData = bytes(input); 25 | 26 | address proxy = deployMetaProxy(address(new Initialize(integrations)), trailingData); 27 | vm.expectRevert(Errors.E_ProxyMetadata.selector); 28 | IComponent(proxy).initialize(msg.sender); 29 | } 30 | 31 | function test_asset_is_a_contract() public { 32 | bytes memory trailingData = abi.encodePacked(bytes4(0), address(0), address(1), address(2)); 33 | 34 | address proxy = deployMetaProxy(address(new Initialize(integrations)), trailingData); 35 | vm.expectRevert(Errors.E_BadAddress.selector); 36 | IComponent(proxy).initialize(msg.sender); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/unit/evault/modules/Token/views.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../EVaultTestBase.t.sol"; 6 | 7 | contract ERC20Test_views is EVaultTestBase { 8 | function test_basicViews() public view { 9 | assertEq(eTST.name(), "EVK Vault eTST-1"); 10 | assertEq(eTST.symbol(), "eTST-1"); 11 | assertEq(eTST.decimals(), assetTST.decimals()); 12 | 13 | assertEq(eTST2.name(), "EVK Vault eTST2-1"); 14 | assertEq(eTST2.symbol(), "eTST2-1"); 15 | assertEq(eTST2.decimals(), assetTST2.decimals()); 16 | } 17 | 18 | function test_vaultSymbolsInSequence() public { 19 | // Numbers are incremented correctly 20 | 21 | for (uint256 i = 2; i < 120; i++) { 22 | IEVault v = IEVault( 23 | factory.createProxy( 24 | address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount) 25 | ) 26 | ); 27 | if (i == 8) assertEq(v.symbol(), "eTST-8"); 28 | else if (i == 34) assertEq(v.symbol(), "eTST-34"); 29 | else if (i == 106) assertEq(v.symbol(), "eTST-106"); 30 | } 31 | 32 | // Each asset has its own counter 33 | 34 | { 35 | IEVault v = IEVault( 36 | factory.createProxy( 37 | address(0), true, abi.encodePacked(address(assetTST2), address(oracle), unitOfAccount) 38 | ) 39 | ); 40 | assertEq(v.symbol(), "eTST2-2"); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/unit/evault/modules/Vault/views.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../EVaultTestBase.t.sol"; 6 | 7 | contract VaultTest_views is EVaultTestBase { 8 | function test_Vault_basicViews() public { 9 | assertEq(eTST.asset(), address(assetTST)); 10 | address creator = makeAddr("creator"); 11 | vm.prank(creator); 12 | address newVault = 13 | factory.createProxy(address(0), true, abi.encodePacked(address(assetTST), address(oracle), unitOfAccount)); 14 | assertEq(IEVault(newVault).creator(), creator); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/unit/evault/shared/CustomTypes.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../EVaultTestBase.t.sol"; 6 | 7 | contract CustomTypesTest is EVaultTestBase { 8 | using TypesLib for uint256; 9 | 10 | function test_maxOwedAndAssetsConversions() external pure { 11 | require(MAX_SANE_DEBT_AMOUNT.toOwed().toAssetsUp().toUint() == MAX_SANE_AMOUNT, "owed to assets up"); 12 | require(MAX_SANE_AMOUNT.toAssets().toOwed().toUint() == MAX_SANE_DEBT_AMOUNT, "assets to owed"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/unit/irm/InterestRateLinearKink.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {IIRM} from "../../../src/InterestRateModels/IIRM.sol"; 6 | import {IRMLinearKink} from "../../../src/InterestRateModels/IRMLinearKink.sol"; 7 | import {MathTesting} from "../../helpers/MathTesting.sol"; 8 | 9 | contract InterestRateLinearKink is Test, MathTesting { 10 | IRMLinearKink irm; 11 | 12 | function setUp() public { 13 | irm = new IRMLinearKink( 14 | // Base=0% APY, Kink(50%)=10% APY Max=300% APY 15 | 0, 16 | 1406417851, 17 | 19050045013, 18 | 2147483648 19 | ); 20 | } 21 | 22 | function test_OnlyVaultCanMutateIRMState() public { 23 | vm.expectRevert(IIRM.E_IRMUpdateUnauthorized.selector); 24 | irm.computeInterestRate(address(1234), 5, 6); 25 | 26 | vm.prank(address(1234)); 27 | irm.computeInterestRate(address(1234), 5, 6); 28 | } 29 | 30 | function test_MaxIR() public view { 31 | uint256 precision = 1e12; 32 | 33 | uint256 ir = getIr(1.0e18); 34 | uint256 SPY = getSPY(3 * 1e17); //300% APY 35 | 36 | assertEq(ir / precision, SPY / precision); 37 | } 38 | 39 | function test_KinkIR() public view { 40 | uint256 precision = 1e12; 41 | 42 | uint256 ir = getIr(0.5e18); 43 | uint256 SPY = getSPY(1 * 1e16); //10% APY 44 | 45 | assertEq(ir / precision, SPY / precision); 46 | } 47 | 48 | function test_UnderKinkIR() public view { 49 | uint256 precision = 1e13; 50 | 51 | uint256 ir = getIr(0.25e18); 52 | uint256 SPY = getSPY(4880875385828198); //4.88% APY 53 | 54 | assertEq(ir / precision, SPY / precision); 55 | } 56 | 57 | function test_OverKinkIR() public view { 58 | uint256 precision = 1e13; 59 | 60 | uint256 ir = getIr(0.75e18); 61 | uint256 SPY = getSPY(109761712896340360); //109.76% APY 62 | 63 | assertEq(ir / precision, SPY / precision); 64 | } 65 | 66 | function getIr(uint256 utilisation) private view returns (uint256) { 67 | require(utilisation <= 1e18, "utilisation can't be > 100%"); 68 | uint256 cash; 69 | uint256 borrows; 70 | 71 | if (utilisation == 1e18) { 72 | borrows = 1e18; 73 | } else { 74 | cash = 1e18; 75 | borrows = cash * utilisation / (1e18 - utilisation); 76 | } 77 | 78 | return irm.computeInterestRateView(address(1234), cash, borrows); 79 | } 80 | 81 | //apy: 500% APY = 5 * 1e17 82 | function getSPY(int128 apy) private pure returns (uint256) { 83 | int256 apr = ln((apy + 1e17) * (2 ** 64) / 1e17); 84 | return uint256(apr) * 1e27 / 2 ** 64 / (365.2425 * 86400); 85 | } 86 | } 87 | --------------------------------------------------------------------------------