├── .fuzz.yml ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ └── bug_report.md ├── executables │ ├── checkAllSpecs │ ├── solc │ └── solc8.24 └── workflows │ ├── checkrules.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── SECURITY.md ├── audits ├── Euler Cantina Code Competition report.pdf ├── Euler Certora report.pdf ├── Euler ChainSecurity report.pdf ├── Euler Hunter Security report.pdf ├── Euler Omniscia report.pdf ├── Euler OpenZeppelin report.pdf ├── Euler Spearbit report.pdf ├── Euler Trail of Bits report.pdf ├── Euler yAudit Code Competition Fixes report.pdf ├── Euler yAudit report (EVC + EVK + Price Oracle).pdf └── Euler yAudit report (EVC).pdf ├── certora ├── conf │ ├── CER-1-Owner │ │ ├── CER-59-Owner-override.conf │ │ └── CER-60-Owner-addressPrefix.conf │ ├── CER-11-Controllers │ │ ├── CER-12-Controllers-number.conf │ │ ├── CER-56-Controllers-disabling.conf │ │ └── CER-81-Controllers-restrictions.conf │ ├── CER-15-Permit │ │ ├── CER-51-Permit-msg-sender.conf │ │ └── CER-65-Permit-onBehalfOfAccount.conf │ ├── CER-2-Operator │ │ ├── CER-52-Operator-deauthorization.conf │ │ ├── CER-54-Operator-owner.conf │ │ ├── CER-62-Operator-setAccountOperator.conf │ │ └── CER-68-Operator-authorization.conf │ ├── CER-20-ChecksDeferrable │ │ ├── CER-22-ChecksDeferrable-controlCollateral.conf │ │ └── CER-23-ChecksDeferrable-callbatch-callback.conf │ ├── CER-27-ExecutionContext │ │ ├── CER-32-ExecutionContext-checksDeferred.conf │ │ ├── CER-33-ExecutionContext-onBehalfOfAccount.conf │ │ └── CER-38-ExecutionContext-restored.conf │ ├── CER-40-AccountStatusCheck │ │ ├── CER-326-checkStatusAll-called.conf │ │ ├── CER-41-AccountStatusCheck-OnlyOne.conf │ │ └── CER-42-AccountStatusCheck-NoController.conf │ ├── CER-44-VaultStatusCheck │ │ └── CER-78-VaultStatusCheck-scheduling.conf │ ├── CER-83-Set.conf │ └── misc │ │ ├── ExecutionContext.conf │ │ └── MustRevertFunctions.conf ├── harness │ ├── EthereumVaultConnectorHarness.sol │ ├── ExecutionContextHarness.sol │ └── SetHarness.sol ├── mutation │ ├── README.md │ ├── conf │ │ └── MutateCER68.conf │ ├── mutants │ │ └── CER-68 │ │ │ └── EthereumVaultConnector.sol │ └── scripts │ │ └── run_mutant_cer68.sh ├── scripts │ └── rerun_all_confs.sh └── specs │ ├── CER-1-Owner │ ├── CER-59-Owner-override.spec │ └── CER-60-Owner-addressPrefix.spec │ ├── CER-11-Controllers │ ├── CER-12-Controllers-number.spec │ ├── CER-56-Controllers-disabling.spec │ └── CER-81-Controllers-restrictions.spec │ ├── CER-15-Permit │ ├── CER-51-Permit-msg-sender.spec │ └── CER-65-Permit-onBehalfOfAccount.spec │ ├── CER-2-Operator │ ├── CER-52-Operator-deauthorization.spec │ ├── CER-54-Operator-owner.spec │ ├── CER-62-Operator-setAccountOperator.spec │ └── CER-68-Operator-authorization.spec │ ├── CER-20-ChecksDeferrable │ ├── CER-22-ChecksDeferrable-controlCollateral.spec │ └── CER-23-ChecksDeferrable-callbatch-callback.spec │ ├── CER-27-ExecutionContext │ ├── CER-32-ExecutionContext-checksDeferred.spec │ ├── CER-33-ExecutionContext-onBehalfOfAccount.spec │ └── CER-38-ExecutionContext-restored.spec │ ├── CER-40-AccountStatusCheck │ ├── CER-326-checkStatusAll-called.spec │ ├── CER-41-AccountStatusCheck-OnlyOne.spec │ └── CER-42-AccountStatusCheck-NoController.spec │ ├── CER-44-VaultStatusCheck │ └── CER-78-VaultStatusCheck-scheduling.spec │ ├── CER-83-Set.spec │ ├── misc │ ├── ExecutionContext.spec │ ├── MustRevertFunctions.spec │ └── Permit-onBehalf-unSpoofable.spec │ └── utils │ ├── ActualCaller.spec │ ├── CallOpSanity.spec │ ├── IsChecksDeferredFunction.spec │ ├── IsLowLevelCallFunction.spec │ └── IsMustRevertFunction.spec ├── docs ├── diagrams │ ├── Direct Vault Interaction Sequence.md │ ├── EVC Batch Vault Interaction Sequence.md │ ├── EVC Call Vault Interaction Sequence.md │ ├── EVC Permit Vault Interaction Sequence.md │ ├── External Calls Sequence.svg │ ├── How EVC Works Example Sequence.md │ ├── Liquidation Interaction Sequence.md │ ├── Liquidation.svg │ └── Typical Interaction.svg ├── specs.md └── whitepaper.md ├── foundry.toml ├── mythril.config.json ├── package.json ├── slither.config.json ├── src ├── Errors.sol ├── EthereumVaultConnector.sol ├── Events.sol ├── ExecutionContext.sol ├── Set.sol ├── TransientStorage.sol ├── interfaces │ ├── IERC1271.sol │ ├── IEthereumVaultConnector.sol │ └── IVault.sol └── utils │ └── EVCUtil.sol └── test ├── evc ├── EthereumVaultConnectorHarness.sol └── EthereumVaultConnectorScribble.sol ├── gas ├── EVCGas.t.sol └── SetGas.t.sol ├── invariants └── EthereumVaultConnector.t.sol ├── unit ├── EVCUtil │ └── EVCUtil.t.sol ├── EthereumVaultConnector │ ├── AccountAndVaultStatus.t.sol │ ├── AccountStatus.t.sol │ ├── Batch.t.sol │ ├── Call.t.sol │ ├── CollateralsManagement.t.sol │ ├── ControlCollateral.t.sol │ ├── ControllersManagement.t.sol │ ├── GetExecutionContext.t.sol │ ├── IsAccountStatusCheckDeferred.t.sol │ ├── IsVaultStatusCheckDeferred.t.sol │ ├── POC.t.sol │ ├── Permit.t.sol │ ├── Receive.t.sol │ ├── SetLockdownMode.t.sol │ ├── SetNonce.t.sol │ ├── SetOperator.t.sol │ ├── SetPermitDisabledMode.t.sol │ └── VaultStatus.t.sol └── Set │ └── Set.t.sol └── utils └── mocks ├── Target.sol └── Vault.sol /.fuzz.yml: -------------------------------------------------------------------------------- 1 | analyze: 2 | remappings: 3 | - "forge-std/=lib/forge-std/src/" 4 | - "openzeppelin/=lib/openzeppelin-contracts/contracts" 5 | solc-version: "0.8.20" 6 | fuzz: 7 | ide: foundry 8 | enable_cheat_codes: true 9 | quick_check: False 10 | build_directory: out 11 | sources_directory: src 12 | project: "EVC" 13 | rpc_url: http://127.0.0.1:8545 14 | deployed_contract_address: "0x5fbdb2315678afecb367f032d93f642f64180aa3" 15 | number_of_cores: 32 16 | time_limit: 1hour 17 | targets: 18 | - "test/evc/EthereumVaultConnectorScribble.sol" 19 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | /src/ @kasperpawlowski 2 | /certora/ @kasperpawlowski -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Bug Description 11 | **Summary:** [A brief overview of the bug] 12 | **Details:** [In-depth explanation of the bug, including how it impacts the system and possible consequences] 13 | 14 | ## Criticality Assessment 15 | **Severity:** [Critical/High/Medium/Low] 16 | **Justification:** [Reasoning behind the severity rating, considering potential impact on security, functionality, and user experience] 17 | 18 | ## Proof of Concept (PoC) 19 | **Step-by-Step Reproduction:** [Clear instructions on how to reproduce the bug] 20 | **Code/Screenshots:** [Relevant code snippets or screenshots; GitHub gists can be used for longer code samples] 21 | **Environment Details:** [Information about the environment where the bug was found, such as contract versions, tools used, etc.] 22 | 23 | ## Impact Analysis 24 | **Affected Components:** [Specify the parts of the system that are impacted by the bug] 25 | **Potential Exploits:** [Discuss how the bug could be potentially exploited and the implications] 26 | 27 | ## Additional Information 28 | **Consistency of Reproduction:** [Indicate the frequency with which the bug can be reproduced] 29 | **Mitigation Suggestions:** [Any recommendations for resolving or mitigating the bug] 30 | 31 | ## Reporter's Contact 32 | **GitHub Username:** [Your GitHub username] 33 | **Email:** [Your email for follow-up discussions] 34 | -------------------------------------------------------------------------------- /.github/executables/checkAllSpecs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from concurrent.futures import ThreadPoolExecutor 4 | import glob 5 | import json 6 | import os 7 | import re 8 | import subprocess 9 | import sys 10 | from threading import Lock 11 | 12 | outputMutex = Lock() 13 | 14 | # Exclude config files that are not verifying from CI 15 | EXCLUDE_CONFS = [ 16 | "certora/conf/CER-15-Permit/CER-51-Permit-msg-sender.conf" 17 | ] 18 | 19 | def listConfigs() -> list[str]: 20 | """List all config files.""" 21 | subdir_confs = glob.glob('certora/conf/**/*.conf', recursive=True) 22 | root_confs = glob.glob('certora/conf/*.conf', recursive=True) 23 | confs = subdir_confs + root_confs 24 | return [x for x in confs if x not in EXCLUDE_CONFS] 25 | 26 | def loadConfigDependencies(filename: str) -> set[str]: 27 | """Load all files a config depends on.""" 28 | try: 29 | conf = json.load(open(filename)) 30 | spec = re.findall('certora/specs/.+\.spec', conf['verify']) 31 | return set([filename, *spec, *conf['files']]) 32 | except: 33 | print(f'invalid json in {filename}!') 34 | return set() 35 | 36 | def listChangedConfigs() -> list[str]: 37 | """List config files that have changed w.r.t. the base branch. If we are not 38 | within a PR branch, list all config files.""" 39 | BASEREF = os.environ.get('GITHUB_BASE_REF', '') 40 | if BASEREF != '': 41 | print('::group::Identifying changed configurations', flush=True) 42 | subprocess.run(['git', 'fetch', 'origin', BASEREF]) 43 | diff = subprocess.run(['git', '-P', 'diff', '--name-only', f'origin/{BASEREF}', 'HEAD'], capture_output=True) 44 | changed = set(diff.stdout.decode().splitlines()) 45 | cfiles = { c: loadConfigDependencies(c) for c in listConfigs() } 46 | changed = [ c for c in cfiles if (not cfiles[c]) or (cfiles[c] & changed)] 47 | print(f'changed configs: {changed}') 48 | print('::endgroup::') 49 | return changed 50 | else: 51 | return listConfigs() 52 | 53 | def runConf(filename): 54 | """Run the configuration in `filename` and return None if everything worked, 55 | or the CompletedProcess when there was some error.""" 56 | print(f'Starting {filename}') 57 | try: 58 | # The commit sha fixes the CI at release version 7.0.2 59 | # from February 2024. 60 | res = subprocess.run(['certoraRun', '--server', 'production', '--wait_for_results', 'all', filename], 61 | capture_output = True) 62 | except Exception as e: 63 | with outputMutex: 64 | print(f'Failed to run {filename}: {e}') 65 | return None 66 | 67 | setattr(res, 'filename', filename) 68 | with outputMutex: 69 | out = res.stdout.decode().strip() 70 | m = re.search('https://[a-z-]+\.certora\.com/output/[0-9a-zA-Z?=/]+', out) 71 | if m: 72 | runurl = m.group(0) 73 | else: 74 | runurl = 'run not available' 75 | print(f'::group::Results for {filename} ({runurl})') 76 | if res.stdout != b'': 77 | print(out) 78 | if res.stderr != b'': 79 | print(f'error output:') 80 | print(res.stderr.decode().strip()) 81 | if res.returncode != 0: 82 | print(f'Failed with {res.returncode}') 83 | print(f'::endgroup::') 84 | return res 85 | print(f'::endgroup::') 86 | return None 87 | 88 | with ThreadPoolExecutor(max_workers=10) as executor: 89 | results = executor.map(runConf, listChangedConfigs()) 90 | 91 | failed = [r for r in results if r is not None] 92 | 93 | if failed: 94 | print('Some jobs failed:') 95 | for r in failed: 96 | print(f'\t{r.filename}') 97 | sys.exit(1) 98 | sys.exit(0) 99 | -------------------------------------------------------------------------------- /.github/executables/solc: -------------------------------------------------------------------------------- 1 | solc8.24 -------------------------------------------------------------------------------- /.github/executables/solc8.24: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/ethereum-vault-connector/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40/.github/executables/solc8.24 -------------------------------------------------------------------------------- /.github/workflows/checkrules.yml: -------------------------------------------------------------------------------- 1 | name: checkrules 2 | 3 | on: 4 | push: 5 | branches: 6 | - certora 7 | pull_request: 8 | 9 | jobs: 10 | checkrules: 11 | name: Run Certora prover on all configs 12 | runs-on: ubuntu-latest 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: true 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | submodules: recursive 20 | 21 | - name: setup cvt 22 | run: | 23 | pip install certora-cli 24 | echo "`pwd`/.github/executables" >> $GITHUB_PATH 25 | 26 | - name: run certora prover on conf files 27 | run: .github/executables/checkAllSpecs 28 | env: 29 | CERTORAKEY: ${{ secrets.CERTORAKEY }} 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 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-c99854277c346fa6de7a8f9837230b36fd85850e 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-c99854277c346fa6de7a8f9837230b36fd85850e 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 -vvv --gas-report --ast 58 | id: test 59 | 60 | coverage: 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-c99854277c346fa6de7a8f9837230b36fd85850e 70 | - name: Run foundry coverage 71 | run: | 72 | FOUNDRY_PROFILE=coverage forge coverage --report summary 73 | id: coverage 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE - VSCode 2 | .vscode/ 3 | 4 | # Logs 5 | logs/ 6 | *.log 7 | 8 | # Dependency directories 9 | node_modules/ 10 | 11 | # Optional npm cache directory 12 | .npm/ 13 | 14 | # Compiler files 15 | cache/ 16 | out/ 17 | 18 | # Ignores development broadcast logs 19 | !/broadcast 20 | /broadcast/*/31337/ 21 | /broadcast/**/dry-run/ 22 | 23 | # Dotenv file 24 | *.env 25 | 26 | # System Files 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # Certora Files 31 | .certora_internal 32 | 33 | emv* -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/openzeppelin-contracts"] 5 | path = lib/openzeppelin-contracts 6 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ethereum Vault Connector 2 | 3 | The Ethereum Vault Connector (EVC) is a foundational layer designed to facilitate the core functionality required for a lending market. It serves as a base building block for various protocols, providing a robust and flexible framework for developers to build upon. The EVC primarily mediates between vaults, contracts that implement the [ERC-4626](https://ethereum.org/en/developers/docs/standards/tokens/erc-4626/) interface and contain additional logic for interfacing with other vaults. The EVC not only provides a common base ecosystem but also reduces complexity in the core lending/borrowing contracts, allowing them to focus on their differentiating factors. 4 | 5 | For more information, visit the [EVC website](https://evc.wtf/) or refer to the [whitepaper](https://github.com/euler-xyz/ethereum-vault-connector/tree/master/docs/whitepaper.md). 6 | 7 | --- 8 | 9 | ## Contracts 10 | 11 | ``` 12 | . 13 | ├── interfaces 14 | │ ├── IERC1271.sol 15 | │ ├── IEthereumVaultConnector.sol 16 | │ └── IVault.sol 17 | ├── utils 18 | │ └── EVCUtil.sol 19 | ├── Errors.sol 20 | ├── EthereumVaultConnector.sol 21 | ├── Events.sol 22 | ├── ExecutionContext.sol 23 | ├── Set.sol 24 | └── TransientStorage.sol 25 | ``` 26 | 27 | ## Install 28 | 29 | To install Ethereum Vault Connector in a [Foundry](https://github.com/foundry-rs/foundry) project: 30 | 31 | ```sh 32 | forge install euler-xyz/ethereum-vault-connector 33 | ``` 34 | 35 | ## Usage 36 | 37 | The Ethereum Vault Connector comes with a comprehensive set of tests written in Solidity, which can be executed using Foundry. 38 | 39 | For a detailed understanding of the Ethereum Vault Connector and considerations for its integration, please refer to the [EVC website](https://evc.wtf/), the [whitepaper](https://github.com/euler-xyz/ethereum-vault-connector/tree/master/docs/whitepaper.md). You can find examples of vaults utilizing the Ethereum Vault Connector in the [EVC Playground](https://github.com/euler-xyz/evc-playground/tree/master/src) repository. However, these example vaults are not meant for production use as they have not been audited and are intended solely for testing and experimentation purposes. 40 | 41 | To install Foundry: 42 | 43 | ```sh 44 | curl -L https://foundry.paradigm.xyz | bash 45 | ``` 46 | 47 | This will download foundryup. To start Foundry, run: 48 | 49 | ```sh 50 | foundryup 51 | ``` 52 | 53 | To clone the repo: 54 | 55 | ```sh 56 | git clone https://github.com/euler-xyz/ethereum-vault-connector.git && cd ethereum-vault-connector 57 | ``` 58 | 59 | ## Testing 60 | 61 | ### in `default` mode 62 | 63 | To run the tests in a `default` mode: 64 | 65 | ```sh 66 | forge test 67 | ``` 68 | 69 | ### with `scribble` annotations 70 | 71 | To run the tests using `scribble` annotations first install [scribble](https://docs.scribble.codes/): 72 | 73 | ```sh 74 | npm install -g eth-scribble 75 | ``` 76 | 77 | To instrument the contracts and run the tests: 78 | 79 | ```sh 80 | scribble test/evc/EthereumVaultConnectorScribble.sol --output-mode files --arm && forge test 81 | ``` 82 | 83 | To remove instrumentation: 84 | 85 | ```sh 86 | scribble test/evc/EthereumVaultConnectorScribble.sol --disarm 87 | ``` 88 | 89 | ### in `coverage` mode 90 | 91 | ```sh 92 | forge coverage 93 | ``` 94 | 95 | ## Safety 96 | 97 | This software is **experimental** and is provided "as is" and "as available". 98 | 99 | **No warranties are provided** and **no liability will be accepted for any loss** incurred through the use of this codebase. 100 | 101 | Always include thorough tests when using the Ethereum Vault Connector to ensure it interacts correctly with your code. 102 | 103 | ## Known limitations 104 | 105 | Refer to the [whitepaper](https://github.com/euler-xyz/ethereum-vault-connector/tree/master/docs/whitepaper.md#security-considerations) for a list of known limitations and security considerations. 106 | 107 | ## Contributing 108 | 109 | The code is currently in an experimental phase. Feedback or ideas for improving the Ethereum Vault Connector are appreciated. Contributions are welcome from anyone interested in conducting security research, writing more tests including formal verification, improving readability and documentation, optimizing, simplifying, or developing integrations. 110 | 111 | ## License 112 | 113 | Licensed under the [GPL-2.0-or-later](https://github.com/euler-xyz/ethereum-vault-connector/tree/master/LICENSE) license. 114 | -------------------------------------------------------------------------------- /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/audits) 22 | 23 | ## Preferred Languages 24 | 25 | We accept security-related inquiries in **English (en)** 26 | -------------------------------------------------------------------------------- /audits/Euler Cantina Code Competition report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/ethereum-vault-connector/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40/audits/Euler Cantina Code Competition report.pdf -------------------------------------------------------------------------------- /audits/Euler Certora report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/ethereum-vault-connector/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40/audits/Euler Certora report.pdf -------------------------------------------------------------------------------- /audits/Euler ChainSecurity report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/ethereum-vault-connector/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40/audits/Euler ChainSecurity report.pdf -------------------------------------------------------------------------------- /audits/Euler Hunter Security report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/ethereum-vault-connector/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40/audits/Euler Hunter Security report.pdf -------------------------------------------------------------------------------- /audits/Euler Omniscia report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/ethereum-vault-connector/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40/audits/Euler Omniscia report.pdf -------------------------------------------------------------------------------- /audits/Euler OpenZeppelin report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/ethereum-vault-connector/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40/audits/Euler OpenZeppelin report.pdf -------------------------------------------------------------------------------- /audits/Euler Spearbit report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/ethereum-vault-connector/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40/audits/Euler Spearbit report.pdf -------------------------------------------------------------------------------- /audits/Euler Trail of Bits report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/ethereum-vault-connector/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40/audits/Euler Trail of Bits report.pdf -------------------------------------------------------------------------------- /audits/Euler yAudit Code Competition Fixes report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/ethereum-vault-connector/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40/audits/Euler yAudit Code Competition Fixes report.pdf -------------------------------------------------------------------------------- /audits/Euler yAudit report (EVC + EVK + Price Oracle).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/ethereum-vault-connector/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40/audits/Euler yAudit report (EVC + EVK + Price Oracle).pdf -------------------------------------------------------------------------------- /audits/Euler yAudit report (EVC).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/euler-xyz/ethereum-vault-connector/64f6d2171a57e02a0f95bcbdecf1d92e9d253d40/audits/Euler yAudit report (EVC).pdf -------------------------------------------------------------------------------- /certora/conf/CER-1-Owner/CER-59-Owner-override.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-1-Owner/CER-59-Owner-override.spec", 6 | "packages": [ 7 | "@openzeppelin=lib/openzeppelin-contracts" 8 | ], 9 | "prover_args": [ 10 | "-useBitVectorTheory true" 11 | ], 12 | "function_finder_mode": "relaxed", 13 | "solc_via_ir" : true, 14 | "assert_autofinder_success" : true, 15 | "optimistic_loop": true, 16 | "optimistic_hashing": true, 17 | "solc_optimize" : "10000", 18 | "msg": "CER-1-Owner/CER-59-Owner-addressPrefix", 19 | "solc": "solc8.24", 20 | "rule_sanity": "basic", 21 | "hashing_length_bound": "256" 22 | } -------------------------------------------------------------------------------- /certora/conf/CER-1-Owner/CER-60-Owner-addressPrefix.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-1-Owner/CER-60-Owner-addressPrefix.spec", 6 | "packages": [ 7 | "@openzeppelin=lib/openzeppelin-contracts" 8 | ], 9 | "prover_args": [ 10 | "-useBitVectorTheory true" 11 | ], 12 | "solc_via_ir" : true, 13 | "assert_autofinder_success" : true, 14 | "optimistic_loop": true, 15 | "optimistic_hashing": true, 16 | "solc_optimize" : "10000", 17 | "msg": "CER-1-Owner/CER-60-Owner-addressPrefix", 18 | "solc": "solc8.24", 19 | "rule_sanity": "basic" 20 | } -------------------------------------------------------------------------------- /certora/conf/CER-11-Controllers/CER-12-Controllers-number.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-11-Controllers/CER-12-Controllers-number.spec", 6 | "solc": "solc8.24", 7 | "solc_optimize": "10000", 8 | "solc_via_ir": true, 9 | "msg": "CER-11-Controllers/CER-12-Controllers-number", 10 | "optimistic_loop": true, 11 | "optimistic_hashing": true, 12 | "rule_sanity": "basic", 13 | "hashing_length_bound": "256" 14 | } -------------------------------------------------------------------------------- /certora/conf/CER-11-Controllers/CER-56-Controllers-disabling.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-11-Controllers/CER-56-Controllers-disabling.spec", 6 | "solc": "solc8.24", 7 | "solc_optimize": "10000", 8 | "solc_via_ir": true, 9 | "msg": "CER-11-Controllers/CER-56-Controllers-disabling", 10 | "optimistic_loop": true, 11 | "optimistic_hashing": true 12 | } -------------------------------------------------------------------------------- /certora/conf/CER-11-Controllers/CER-81-Controllers-restrictions.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-11-Controllers/CER-81-Controllers-restrictions.spec", 6 | "solc": "solc8.24", 7 | "solc_optimize": "10000", 8 | "solc_via_ir": true, 9 | "msg": "CER-11-Controllers/CER-81-Controllers-restrictions", 10 | "optimistic_loop": true, 11 | "optimistic_hashing": true, 12 | } -------------------------------------------------------------------------------- /certora/conf/CER-15-Permit/CER-51-Permit-msg-sender.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-15-Permit/CER-51-Permit-msg-sender.spec", 6 | "packages": [ 7 | "@openzeppelin=lib/openzeppelin-contracts" 8 | ], 9 | "solc_via_ir" : true, 10 | "rule_sanity": "basic", 11 | "assert_autofinder_success" : true, 12 | "optimistic_loop": true, 13 | "optimistic_hashing": true, 14 | "solc_optimize" : "10000", 15 | "msg": "CER-15-Permit/CER-51-Permit-msg-sender", 16 | "solc": "solc8.24" 17 | } 18 | -------------------------------------------------------------------------------- /certora/conf/CER-15-Permit/CER-65-Permit-onBehalfOfAccount.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-15-Permit/CER-65-Permit-onBehalfOfAccount.spec", 6 | "packages": [ 7 | "@openzeppelin=lib/openzeppelin-contracts" 8 | ], 9 | "solc_via_ir" : true, 10 | "rule_sanity": "basic", 11 | "assert_autofinder_success" : true, 12 | "optimistic_loop": true, 13 | "optimistic_hashing": true, 14 | "solc_optimize" : "10000", 15 | "msg": "CER-15-Permit/CER-65-Permit-onBehalfOfAccount", 16 | "solc": "solc8.24", 17 | "hashing_length_bound": "256" 18 | } 19 | -------------------------------------------------------------------------------- /certora/conf/CER-2-Operator/CER-52-Operator-deauthorization.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-2-Operator/CER-52-Operator-deauthorization.spec", 6 | "solc": "solc8.24", 7 | "solc_optimize": "10000", 8 | "rule_sanity": "basic", 9 | "solc_via_ir": true, 10 | "msg": "CER-2-Operator/CER-52-Operator-deauthorization", 11 | "optimistic_loop": true, 12 | "optimistic_hashing": true, 13 | "prover_args": ["-smt_bitVectorTheory", "true"], 14 | "hashing_length_bound": "256" 15 | } -------------------------------------------------------------------------------- /certora/conf/CER-2-Operator/CER-54-Operator-owner.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-2-Operator/CER-54-Operator-owner.spec", 6 | "solc": "solc8.24", 7 | "solc_optimize": "10000", 8 | "rule_sanity": "basic", 9 | "solc_via_ir": true, 10 | "msg": "CER-2-Operator/CER-54-Operator-owner", 11 | "optimistic_loop": true, 12 | "optimistic_hashing": true, 13 | "prover_args": ["-smt_bitVectorTheory", "true"] 14 | } -------------------------------------------------------------------------------- /certora/conf/CER-2-Operator/CER-62-Operator-setAccountOperator.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-2-Operator/CER-62-Operator-setAccountOperator.spec", 6 | "solc_via_ir" : true, 7 | "rule_sanity": "basic", 8 | "assert_autofinder_success" : true, 9 | "optimistic_loop": true, 10 | "optimistic_hashing": true, 11 | "solc_optimize" : "10000", 12 | "msg" : "CER-2-Operator/CER-62-Operator-setAccountOperator", 13 | "solc": "solc8.24" 14 | } -------------------------------------------------------------------------------- /certora/conf/CER-2-Operator/CER-68-Operator-authorization.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-2-Operator/CER-68-Operator-authorization.spec", 6 | "solc": "solc8.24", 7 | "solc_optimize": "10000", 8 | "rule_sanity": "basic", 9 | "solc_via_ir": true, 10 | "msg": "CER-2-Operator/CER-68-Operator-authorization", 11 | "optimistic_loop": true, 12 | "optimistic_hashing": true, 13 | "prover_args": ["-smt_bitVectorTheory", "true"], 14 | "hashing_length_bound": "256" 15 | } -------------------------------------------------------------------------------- /certora/conf/CER-20-ChecksDeferrable/CER-22-ChecksDeferrable-controlCollateral.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "prover_args": ["-smt_bitVectorTheory", "true"], 6 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-20-ChecksDeferrable/CER-22-ChecksDeferrable-controlCollateral.spec", 7 | "solc_via_ir" : true, 8 | "rule_sanity": "basic", 9 | "assert_autofinder_success" : true, 10 | "optimistic_loop": true, 11 | "optimistic_hashing": true, 12 | "solc_optimize" : "10000", 13 | "msg": "CER-20-ChecksDeferrable/CER-22-ChecksDeferrable-controlCollateral", 14 | "solc": "solc8.24" 15 | } 16 | -------------------------------------------------------------------------------- /certora/conf/CER-20-ChecksDeferrable/CER-23-ChecksDeferrable-callbatch-callback.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "src/EthereumVaultConnector.sol", 4 | "certora/harness/EthereumVaultConnectorHarness.sol" 5 | ], 6 | "prover_args": ["-smt_bitVectorTheory", "true"], 7 | "function_finder_mode": "relaxed", 8 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-20-ChecksDeferrable/CER-23-ChecksDeferrable-callbatch-callback.spec", 9 | "solc_via_ir" : true, 10 | "rule_sanity": "basic", 11 | "assert_autofinder_success" : true, 12 | "optimistic_loop": true, 13 | "optimistic_hashing": true, 14 | "solc_optimize" : "10000", 15 | "msg": "CER-20-ChecksDeferrable/CER-23-ChecksDeferrable-callbatch-callback", 16 | "solc": "solc8.24", 17 | } 18 | -------------------------------------------------------------------------------- /certora/conf/CER-27-ExecutionContext/CER-32-ExecutionContext-checksDeferred.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "prover_args": ["-smt_bitVectorTheory", "true"], 6 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-27-ExecutionContext/CER-32-ExecutionContext-checksDeferred.spec", 7 | "solc_via_ir" : true, 8 | "rule_sanity": "basic", 9 | "assert_autofinder_success" : true, 10 | "optimistic_loop": true, 11 | "optimistic_hashing": true, 12 | "solc_optimize" : "10000", 13 | "msg": "CER-27-ExecutionContext/CER-32-ExecutionContext-checksDeferred", 14 | "solc": "solc8.24" 15 | } 16 | -------------------------------------------------------------------------------- /certora/conf/CER-27-ExecutionContext/CER-33-ExecutionContext-onBehalfOfAccount.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-27-ExecutionContext/CER-33-ExecutionContext-onBehalfOfAccount.spec", 6 | "solc_via_ir" : true, 7 | "function_finder_mode": "relaxed", 8 | "rule_sanity": "basic", 9 | "assert_autofinder_success" : true, 10 | "optimistic_loop": true, 11 | "optimistic_hashing": true, 12 | "solc_optimize" : "10000", 13 | "msg": "CER-27-ExecutionContext/CER-33-ExecutionContext-onBehalfOfAccount", 14 | "solc": "solc8.24" 15 | } 16 | -------------------------------------------------------------------------------- /certora/conf/CER-27-ExecutionContext/CER-38-ExecutionContext-restored.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-27-ExecutionContext/CER-38-ExecutionContext-restored.spec", 6 | "solc_via_ir" : true, 7 | "rule_sanity": "basic", 8 | "assert_autofinder_success" : true, 9 | "optimistic_loop": true, 10 | "optimistic_hashing": true, 11 | "solc_optimize" : "10000", 12 | "msg": "CER-27-ExecutionContext/CER-38-ExecutionContext-restored", 13 | "solc": "solc8.24", 14 | "hashing_length_bound": "256" 15 | } 16 | -------------------------------------------------------------------------------- /certora/conf/CER-40-AccountStatusCheck/CER-326-checkStatusAll-called.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-40-AccountStatusCheck/CER-326-checkStatusAll-called.spec", 6 | "solc": "solc8.24", 7 | "solc_optimize": "10000", 8 | "rule_sanity": "basic", 9 | "solc_via_ir": true, 10 | "msg": "CER-40-AccountStatusCheck/CER-326-checkStatusAll-called", 11 | "optimistic_loop": true, 12 | "optimistic_hashing": true, 13 | "coverage_info": "advanced", 14 | "prover_args": ["-smt_bitVectorTheory", "true"] 15 | } -------------------------------------------------------------------------------- /certora/conf/CER-40-AccountStatusCheck/CER-41-AccountStatusCheck-OnlyOne.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-40-AccountStatusCheck/CER-41-AccountStatusCheck-OnlyOne.spec", 6 | "solc": "solc8.24", 7 | "solc_optimize": "10000", 8 | "rule_sanity": "basic", 9 | "solc_via_ir": true, 10 | "msg": "CER-40-AccountStatusCheck/CER-41-AccountStatusCheck-OnlyOne", 11 | "optimistic_loop": true, 12 | "optimistic_hashing": true, 13 | "prover_args": ["-smt_bitVectorTheory", "true"] 14 | } -------------------------------------------------------------------------------- /certora/conf/CER-40-AccountStatusCheck/CER-42-AccountStatusCheck-NoController.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-40-AccountStatusCheck/CER-42-AccountStatusCheck-NoController.spec", 6 | "solc": "solc8.24", 7 | "solc_optimize": "10000", 8 | "rule_sanity": "basic", 9 | "solc_via_ir": true, 10 | "msg": "CER-40-AccountStatusCheck/CER-42-AccountStatusCheck-NoController", 11 | "optimistic_loop": true, 12 | "optimistic_hashing": true, 13 | "prover_args": ["-smt_bitVectorTheory", "true"] 14 | } -------------------------------------------------------------------------------- /certora/conf/CER-44-VaultStatusCheck/CER-78-VaultStatusCheck-scheduling.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/CER-44-VaultStatusCheck/CER-78-VaultStatusCheck-scheduling.spec", 6 | "parametric_contracts": ["EthereumVaultConnectorHarness"], 7 | "solc_via_ir" : true, 8 | "function_finder_mode" : "relaxed", 9 | "rule_sanity": "basic", 10 | "assert_autofinder_success" : true, 11 | "optimistic_loop": true, 12 | "optimistic_hashing": true, 13 | "solc_optimize" : "10000", 14 | "msg" : "CER-44-VaultStatusCheck/CER-78-VaultStatusCheck-scheduling", 15 | "solc": "solc8.24", 16 | "hashing_length_bound": "256" 17 | } -------------------------------------------------------------------------------- /certora/conf/CER-83-Set.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/SetHarness.sol" 4 | ], 5 | "verify": "SetHarness:certora/specs/CER-83-Set.spec", 6 | "solc": "solc8.24", 7 | "loop_iter" : "6", 8 | "msg": "Set properties", 9 | "optimistic_loop": true, 10 | "rule_sanity" : "basic", 11 | "coverage_info" : "advanced", 12 | "mutations": { 13 | "gambit": { 14 | "filename" : "src/Set.sol", 15 | "num_mutants": 25, 16 | "functions": ["insert","remove"], 17 | }, 18 | "msg": "Set mutations" 19 | } 20 | } -------------------------------------------------------------------------------- /certora/conf/misc/ExecutionContext.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/ExecutionContextHarness.sol" 4 | ], 5 | "verify": "ExecutionContextHarness:certora/specs/misc/ExecutionContext.spec", 6 | "solc": "solc8.24", 7 | "solc_optimize": "10000", 8 | "solc_via_ir": true, 9 | "msg": "misc/ExecutionContext", 10 | "optimistic_loop": true, 11 | "rule_sanity": "basic" 12 | } -------------------------------------------------------------------------------- /certora/conf/misc/MustRevertFunctions.conf: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "certora/harness/EthereumVaultConnectorHarness.sol" 4 | ], 5 | "verify": "EthereumVaultConnectorHarness:certora/specs/misc/MustRevertFunctions.spec", 6 | "packages": [ 7 | "@openzeppelin=lib/openzeppelin-contracts" 8 | ], 9 | "solc_via_ir" : true, 10 | "rule_sanity": "basic", 11 | "assert_autofinder_success" : true, 12 | "optimistic_loop": true, 13 | "optimistic_hashing": true, 14 | "solc_optimize" : "10000", 15 | "msg": "misc/MustRevertFunctions", 16 | "solc": "solc8.24", 17 | "hashing_length_bound": "256" 18 | } 19 | -------------------------------------------------------------------------------- /certora/harness/EthereumVaultConnectorHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/EthereumVaultConnector.sol"; 6 | import "../../src/ExecutionContext.sol"; 7 | import "../../src/Set.sol"; 8 | 9 | contract EthereumVaultConnectorHarness is EthereumVaultConnector { 10 | using ExecutionContext for EC; 11 | using Set for SetStorage; 12 | 13 | function getExecutionContextDefault() external view returns (uint256) { 14 | return 1 << ExecutionContext.STAMP_OFFSET; 15 | } 16 | 17 | function getExecutionContextAreChecksDeferred() external view returns (bool) { 18 | return executionContext.areChecksDeferred(); 19 | } 20 | 21 | function numOfController(address account) public view returns (uint8) { 22 | return accountControllers[account].numElements; 23 | } 24 | 25 | function accountCollateralsContains(address account, address collateral) public view returns (bool) { 26 | return accountCollaterals[account].contains(collateral); 27 | } 28 | 29 | function getExecutionContextOnBehalfOfAccount() external view returns (address) { 30 | return executionContext.getOnBehalfOfAccount(); 31 | } 32 | 33 | function getOwnerOf(bytes19 prefix) public view returns (address) { 34 | return ownerLookup[prefix].owner; 35 | } 36 | 37 | function checkAccountStatus(address account) public returns (bool) { 38 | (bool status, ) = checkAccountStatusInternal(account); 39 | return status; 40 | } 41 | 42 | function requireAccountStatusCheckInternalHarness(address account) public { 43 | requireAccountStatusCheckInternal(account); 44 | } 45 | function checkVaultStatus(address vault) public returns (bool) { 46 | (bool isValid, ) = checkVaultStatusInternal(vault); 47 | return isValid; 48 | } 49 | function areAccountStatusChecksEmpty() public view returns (bool) { 50 | return accountStatusChecks.numElements == 0; 51 | } 52 | 53 | function areVaultStatusChecksEmpty() public view returns (bool) { 54 | return vaultStatusChecks.numElements == 0; 55 | } 56 | 57 | function getOperatorFromAddress(address account, address operator) external view returns (uint256) { 58 | bytes19 addressPrefix = getAddressPrefixInternal(account); 59 | return operatorLookup[addressPrefix][operator]; 60 | } 61 | 62 | function getAccountCollaterals(address account) external view returns (address[] memory) { 63 | return accountCollaterals[account].get(); 64 | } 65 | 66 | function getAccountController(address account) public view returns (address) { 67 | return accountControllers[account].firstElement; 68 | } 69 | 70 | function isAccountController(address account, address controller) public view returns (bool) { 71 | return accountControllers[account].contains(controller); 72 | } 73 | 74 | function containsStatusCheckFor(address account) public view returns (bool) { 75 | return accountStatusChecks.contains(account); 76 | } 77 | 78 | function selfCallSuccessCheck(uint256 value, bytes calldata data) external returns (bool) { 79 | (bool success, ) = address(this).call{value: value}(data); 80 | return success; 81 | } 82 | function ecGetOnBehalfOfAccount(EC context) external pure returns (address result) { 83 | result = ExecutionContext.getOnBehalfOfAccount(context); 84 | } 85 | function isPhantomAccountContract(bytes19 addressPrefix) external view returns (bool) { 86 | return address(uint160(uint152(addressPrefix)) << 8).code.length != 0; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /certora/harness/ExecutionContextHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/ExecutionContext.sol"; 6 | 7 | contract ExecutionContextHarness { 8 | uint public constant ON_BEHALF_OF_ACCOUNT_MASK = ExecutionContext.ON_BEHALF_OF_ACCOUNT_MASK; 9 | uint public constant OPERATOR_AUTHENTICATED_MASK = ExecutionContext.OPERATOR_AUTHENTICATED_MASK; 10 | uint public constant SIMULATION_MASK = ExecutionContext.SIMULATION_MASK; 11 | uint public constant STAMP_OFFSET = ExecutionContext.STAMP_OFFSET; 12 | 13 | function areChecksDeferred(EC context) external pure returns (bool result) { 14 | result = ExecutionContext.areChecksDeferred(context); 15 | } 16 | 17 | function getOnBehalfOfAccount(EC context) external pure returns (address result) { 18 | result = ExecutionContext.getOnBehalfOfAccount(context); 19 | } 20 | 21 | function setOnBehalfOfAccount(EC context, address account) external pure returns (EC result) { 22 | result = ExecutionContext.setOnBehalfOfAccount(context,account); 23 | } 24 | 25 | function areChecksInProgress(EC context) external pure returns (bool result) { 26 | result = ExecutionContext.areChecksInProgress(context); 27 | } 28 | 29 | function setChecksInProgress(EC context) external pure returns (EC result) { 30 | result = ExecutionContext.setChecksInProgress(context); 31 | } 32 | 33 | function isOperatorAuthenticated(EC context) external pure returns (bool result) { 34 | result = ExecutionContext.isOperatorAuthenticated(context); 35 | } 36 | 37 | function setOperatorAuthenticated(EC context) external pure returns (EC result) { 38 | result = ExecutionContext.setOperatorAuthenticated(context); 39 | } 40 | 41 | function clearOperatorAuthenticated(EC context) external pure returns (EC result) { 42 | result = ExecutionContext.clearOperatorAuthenticated(context); 43 | } 44 | 45 | function isSimulationInProgress(EC context) external pure returns (bool result) { 46 | result = ExecutionContext.isSimulationInProgress(context); 47 | } 48 | 49 | function setSimulationInProgress(EC context) external pure returns (EC result) { 50 | result = ExecutionContext.setSimulationInProgress(context); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /certora/harness/SetHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../src/Set.sol"; 6 | 7 | contract SetHarness { 8 | 9 | SetStorage public setStorage; 10 | 11 | function insert( 12 | address element 13 | ) external returns (bool wasInserted) { 14 | return Set.insert(setStorage, element); 15 | } 16 | 17 | function remove( 18 | address element 19 | ) external returns (bool) { 20 | return Set.remove(setStorage, element); 21 | } 22 | 23 | function reorder(uint8 index1, uint8 index2) external { 24 | Set.reorder(setStorage, index1, index2); 25 | } 26 | 27 | function contains(address elem) external view returns (bool) { 28 | return Set.contains(setStorage, elem); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /certora/mutation/README.md: -------------------------------------------------------------------------------- 1 | # Mutation Testing 2 | This directory contains files relevant to mutation testing using certoraMutate. 3 | More information about certoraMutate and Gambit can be found 4 | [here](https://docs.certora.com/en/latest/docs/gambit/mutation-verifier.html). 5 | At the time of writing Gambit is used exclusively to test rules against manually 6 | crafted bug injections. 7 | 8 | # Directory structure 9 | - conf: configuration files for mutation testing 10 | - mutants: solidity contracts modified to include bug injections 11 | - scripts: run scripts to do testing 12 | 13 | 14 | # Instructions 15 | If you've installed `certora-cli` then `certoraMutate` is already installed as 16 | well. Simply run `cd` into the root directory for this repository and run the 17 | script related to the bug of interest in the scripts directory. 18 | 19 | # Bug Descriptions 20 | Here we describe each mutant. The changes can easily be found within the mutated file by 21 | searching for a comment with the word "Mutate" 22 | 23 | ### Mutant CER-68 24 | This mutant is based on a prior audit report which found a bug in EthereumVaultConnector.sol 25 | The change is as follows: 26 | ``` 27 | diff --git a/src/EthereumVaultConnector.sol b/src/EthereumVaultConnector.sol 28 | index 1c0a327..4316f49 100644 29 | --- a/src/EthereumVaultConnector.sol 30 | +++ b/src/EthereumVaultConnector.sol 31 | @@ -310,7 +310,7 @@ contract EthereumVaultConnector is Events, Errors, TransientStorage, IEVC { 32 | address owner = haveCommonOwnerInternal(account, msgSender) ? msgSender : getAccountOwnerInternal(account); 33 | 34 | // if it's an operator calling, it can only act for itself and must not be able to change other operators status 35 | - if (owner != msgSender && operator != msgSender) { 36 | + if (owner != msg.sender && operator != msg.sender && address(this) != msg.sender) { 37 | revert EVC_NotAuthorized(); 38 | } 39 | 40 | ``` -------------------------------------------------------------------------------- /certora/mutation/conf/MutateCER68.conf: -------------------------------------------------------------------------------- 1 | { 2 | "gambit": { 3 | "filename": "../../../src/EthereumVaultConnector.sol", 4 | "num_mutants": 0 5 | }, 6 | "manual_mutants": { 7 | "../../../src/EthereumVaultConnector.sol": "../mutants/CER-68" 8 | } 9 | } -------------------------------------------------------------------------------- /certora/mutation/scripts/run_mutant_cer68.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Run mutation testing for CER-68 from root directory 3 | certoraMutate --prover_conf certora/conf/CER-2-Operator/CER-68-Operator-authorization.conf --mutation_conf certora/mutation/conf/MutateCER68.conf --server production --msg "manual mutant for CER-68" -------------------------------------------------------------------------------- /certora/scripts/rerun_all_confs.sh: -------------------------------------------------------------------------------- 1 | find ./certora/conf -name '*.conf' -exec echo "running on" {} \; -exec certoraRun {} --server production \; 2 | -------------------------------------------------------------------------------- /certora/specs/CER-1-Owner/CER-59-Owner-override.spec: -------------------------------------------------------------------------------- 1 | // CER-59: EVC MUST NOT override the already stored Owner for a 2 | // given addressPrefix 3 | import "../utils/IsMustRevertFunction.spec"; 4 | 5 | persistent ghost bool overwroteOwner { 6 | init_state axiom overwroteOwner == false; 7 | } 8 | 9 | hook Sstore currentContract.ownerLookup[KEY bytes19 address_prefix].owner address newValue (address oldValue) { 10 | if (oldValue != 0 && newValue != oldValue) { 11 | overwroteOwner = true; 12 | } 13 | } 14 | 15 | rule neverOverwriteOwner (method f) filtered {f -> 16 | !isMustRevertFunction(f) 17 | }{ 18 | env e; 19 | calldataarg args; 20 | require !overwroteOwner; 21 | f(e, args); 22 | assert !overwroteOwner; 23 | } -------------------------------------------------------------------------------- /certora/specs/CER-1-Owner/CER-60-Owner-addressPrefix.spec: -------------------------------------------------------------------------------- 1 | 2 | methods{ 3 | function getAddressPrefix(address) external returns (bytes19) envfree; 4 | function haveCommonOwner(address, address) external returns (bool) envfree; 5 | } 6 | 7 | //check that two addresses with the same prefix also have a common owner 8 | rule check_have_commonPrefix(){ 9 | address x; 10 | address y; 11 | bytes19 prefixX = getAddressPrefix(x); 12 | bytes19 prefixY = getAddressPrefix(y); 13 | 14 | bool haveCommonOwner = haveCommonOwner(x,y); 15 | 16 | assert haveCommonOwner <=> prefixX == prefixY; 17 | } 18 | -------------------------------------------------------------------------------- /certora/specs/CER-11-Controllers/CER-12-Controllers-number.spec: -------------------------------------------------------------------------------- 1 | /** 2 | CER-12: 3 | Each Account can have at most one Controller Vault enabled at a time unless it's a transient state during a Checks-deferrable Call. This is how single-liability-per-account is enforced. 4 | **/ 5 | 6 | import "../utils/IsMustRevertFunction.spec"; 7 | 8 | methods { 9 | function numOfController(address account) external returns(uint8) envfree; 10 | function getExecutionContextAreChecksDeferred() external returns(bool) envfree; 11 | function containsStatusCheckFor(address account) external returns(bool) envfree; 12 | } 13 | 14 | /** 15 | * Check that we never have more than one controller. 16 | * 17 | * It does not work for batch() or call(), as it requires rather complex reasoning. Some pieces necessary for this reasoning: 18 | * - the account status checks verifies that there is only a single controller. 19 | * - the only place that adds a controller issues an account status check for 20 | * the respective account, either immediately or by registering it to the set. 21 | * - whenever the setDeferredChecks bit in `executionContext` is cleared, 22 | * we make sure that we run the account status checks for 23 | * all accounts registered for a status check. 24 | * 25 | * We prove this separately for enableController as we need to break it into 26 | * cases (when checks are deferred vs not). This is done in the 3 rules that 27 | * follow this one. 28 | */ 29 | rule onlyOneController(method f) filtered { f-> 30 | !isMustRevertFunction(f) 31 | && f.selector != sig:batch(IEVC.BatchItem[] calldata).selector 32 | && f.selector != sig:call(address, address, uint256, bytes calldata).selector 33 | && f.selector != sig:enableController(address, address).selector 34 | } { 35 | env e; 36 | calldataarg args; 37 | address a; 38 | require numOfController(a) <= 1; 39 | f(e, args); 40 | assert numOfController(a) <= 1; 41 | } 42 | 43 | // For enableController, we can only check this rule directly if 44 | // account status checks are not deferred. To cover the deferred case, 45 | // we check that when account checks are deferred, the enableController 46 | // call will enqueue a check for the relevant account using the rule 47 | // `enableControllerEnqueuesStatusCheckWhenDeferred` 48 | rule onlyOneControllerEnableController { 49 | env e; 50 | address account; 51 | address vault; 52 | require !getExecutionContextAreChecksDeferred(); 53 | require numOfController(account) <= 1; 54 | enableController(e, account, vault); 55 | assert numOfController(account) <= 1; 56 | } 57 | 58 | // This ensures enableController will enqueue a status 59 | // check when status checks are deferred 60 | rule enableControllerEnequeusStatusCheckWhenDeferred { 61 | env e; 62 | address account; 63 | address vault; 64 | require getExecutionContextAreChecksDeferred(); 65 | enableController(e, account, vault); 66 | assert containsStatusCheckFor(account) == true; 67 | } 68 | 69 | // This directly specifies that checkAccountStatusInternal 70 | // will force numOfController for the checked account to be 71 | // less or equal to one to cover deferred checks. 72 | rule checkAccountStatusForcesNumControllersLEOne { 73 | env e; 74 | address account; 75 | bool status = checkAccountStatus(e, account); 76 | assert numOfController(account) > 1 => !status; 77 | } -------------------------------------------------------------------------------- /certora/specs/CER-11-Controllers/CER-56-Controllers-disabling.spec: -------------------------------------------------------------------------------- 1 | // CER-56: Only an enabled Controller Vault for the Account MUST be allowed to 2 | // disable itself for that Account. 3 | 4 | // This implementation splits this specification into these rules: 5 | // - an enabled controller cannot be disabled by some other address 6 | // - an enabled controller vault can successfully call disableController 7 | 8 | methods { 9 | function isControllerEnabled(address account, address vault) external returns (bool) envfree; 10 | } 11 | 12 | // an enabled controller cannot be disabled by some other address 13 | rule non_enabled_controller_cannot_disable_controller { 14 | env e; 15 | address account; 16 | address otherController; 17 | // If otherController is an enabledController... 18 | require isControllerEnabled(account, otherController); 19 | // ... and some other address calls disableController... 20 | require e.msg.sender != otherController; 21 | disableController(e, account); 22 | // ... the otherController will not have been disabled 23 | assert isControllerEnabled(account, otherController); 24 | } 25 | 26 | // an enabled controller can call disableController to disable itself 27 | rule enabled_controller_can_disable_itself { 28 | env e; 29 | address account; 30 | require isControllerEnabled(account, e.msg.sender); 31 | disableController@withrevert(e, account); 32 | satisfy !lastReverted && !isControllerEnabled(account, e.msg.sender); 33 | } -------------------------------------------------------------------------------- /certora/specs/CER-11-Controllers/CER-81-Controllers-restrictions.spec: -------------------------------------------------------------------------------- 1 | methods { 2 | function isControllerEnabled(address account, address vault) external returns (bool) envfree; 3 | } 4 | 5 | // CER-81: EVC MUST NOT be allowed to become Controller 6 | // Note: this does not work for batch or call because these involve 7 | // CALLs which are complicated to reason about 8 | invariant evc_cannot_become_controller(address account) 9 | !isControllerEnabled(account, currentContract) 10 | filtered { 11 | f -> f.selector != sig:batch(IEVC.BatchItem[] calldata).selector 12 | && f.selector != sig:call(address, address, uint256, bytes calldata).selector 13 | } 14 | 15 | -------------------------------------------------------------------------------- /certora/specs/CER-15-Permit/CER-51-Permit-msg-sender.spec: -------------------------------------------------------------------------------- 1 | import "../utils/IsMustRevertFunction.spec"; 2 | 3 | /** 4 | * Check that we don't leak write access to storage to other code. 5 | */ 6 | import "../utils/CallOpSanity.spec"; 7 | 8 | methods { 9 | function getAccountController(address account) external returns (address) envfree; 10 | function isCollateralEnabled(address account, address vault) external returns (bool) envfree; 11 | function isAccountController(address account, address controller) external returns (bool) envfree; 12 | 13 | } 14 | 15 | //////////////////////////////////////////////////////////////// 16 | // // 17 | // Account Controllers (Ghost and Hooks) // 18 | // // 19 | //////////////////////////////////////////////////////////////// 20 | 21 | /* Any load from `firstElement` or `elements`, for either `accountControllers` 22 | * or `vaultStatusChecks`, MUST be dominated by a store on the same memory first. 23 | * Thus, requiring `value != currentContract` is safe as we assert it before. 24 | * We do this to keep the knowledge about `currentContract` not being in any of 25 | * these sets across HAVOC'd function calls. 26 | */ 27 | // when writing to accountControllers, check that value != currentContract 28 | hook Sstore EthereumVaultConnectorHarness.accountControllers[KEY address user].firstElement address value { 29 | assert(value != currentContract); 30 | } 31 | hook Sstore EthereumVaultConnectorHarness.accountControllers[KEY address user].elements[INDEX uint256 i].value address value { 32 | assert(value != currentContract); 33 | } 34 | // when loading from accountControllers, we know that value != currentContract 35 | hook Sload address value EthereumVaultConnectorHarness.accountControllers[KEY address user].firstElement { 36 | require(value != currentContract); 37 | } 38 | hook Sload address value EthereumVaultConnectorHarness.accountControllers[KEY address user].elements[INDEX uint256 i].value { 39 | require(value != currentContract); 40 | } 41 | 42 | //////////////////////////////////////////////////////////////// 43 | // // 44 | // Vault Status Checks (Ghost and Hooks) // 45 | // // 46 | //////////////////////////////////////////////////////////////// 47 | 48 | // when writing to vaultStatusChecks, check that value != currentContract 49 | hook Sstore EthereumVaultConnectorHarness.vaultStatusChecks.firstElement address value { 50 | assert(value != currentContract); 51 | } 52 | hook Sstore EthereumVaultConnectorHarness.vaultStatusChecks.elements[INDEX uint256 i].value address value { 53 | assert(value != currentContract); 54 | } 55 | // when loading from vaultStatusChecks, we know that value != currentContract 56 | hook Sload address value EthereumVaultConnectorHarness.vaultStatusChecks.firstElement { 57 | require(value != currentContract); 58 | } 59 | hook Sload address value EthereumVaultConnectorHarness.vaultStatusChecks.elements[INDEX uint256 i].value { 60 | require(value != currentContract); 61 | } 62 | 63 | //////////////////////////////////////////////////////////////// 64 | // // 65 | // Ghost and Hook for Property // 66 | // EVC can only be msg.sender during the permit() function // 67 | // // 68 | //////////////////////////////////////////////////////////////// 69 | 70 | /** 71 | * Core property: we never `call` into ourselves (except for `permit`, which we 72 | * exclude in the rule below). All other possibilities to be called either have 73 | * `msg.sender != currentContract` (reentrant `call` or `staticcall` or internal 74 | * `delegatecall`), or have no write access to our storage (reentrant 75 | * `delegatecall`). 76 | */ 77 | hook CALL(uint g, address addr, uint value, uint argsOffset, uint argsLength, uint retOffset, uint retLength) uint rc 78 | { 79 | assert(executingContract != currentContract || addr != currentContract); 80 | } 81 | 82 | // This rule checks the property of interest "EVC can only be msg.sender during the self-call in the permit() function. Expected to fail on permit() function. 83 | // To prove this for controlCollateral, we need the additionl assumption 84 | // that the EVC can never become an account controller. (See the rule after this one). We also prove this assumption 85 | // CER-80: https://linear.app/euler-labs/issue/CER-80/collaterals-restrictions 86 | 87 | rule onlyEVCCanCallCriticalMethod(method f, env e, calldataarg args) 88 | filtered {f -> 89 | !isMustRevertFunction(f) && 90 | f.selector != sig:EthereumVaultConnectorHarness.permit(address,address,uint256,uint256,uint256,uint256,bytes,bytes).selector && 91 | f.selector != sig:EthereumVaultConnectorHarness.controlCollateral(address, address, uint256, bytes).selector 92 | }{ 93 | //Exclude EVC as being the initiator of the call. 94 | require(e.msg.sender != currentContract); 95 | f(e,args); 96 | 97 | assert(true); 98 | } 99 | 100 | // For onlyController we need the additional assumption that 101 | // the EVC ("currentContract") can never become a collateral for any address. 102 | // NOTE: We will eventually prove this assumption to satisfy 103 | // CER-80: https://linear.app/euler-labs/issue/CER-80/collaterals-restrictions 104 | rule onlyEVCCanCallCriticalMethodOnlyController { 105 | env e; 106 | address targetCollateral; 107 | address onBehalfOfAccount; 108 | uint256 value; 109 | bytes data; 110 | 111 | require(e.msg.sender != currentContract); 112 | require !isCollateralEnabled(onBehalfOfAccount, currentContract); 113 | controlCollateral(e, targetCollateral, onBehalfOfAccount, value, data); 114 | 115 | assert(true); 116 | } 117 | -------------------------------------------------------------------------------- /certora/specs/CER-15-Permit/CER-65-Permit-onBehalfOfAccount.spec: -------------------------------------------------------------------------------- 1 | import "../utils/ActualCaller.spec"; 2 | import "../utils/IsMustRevertFunction.spec"; 3 | 4 | methods { 5 | function _.getOnBehalfOfAccount(uint256 ec) internal with (env e) => getOnBehalfOfAccountCheckInEVC(e, ec) expect (address); 6 | } 7 | 8 | // CER-65: EVC MUST only rely on the stored Execution Context's 9 | // onBehalfOfAccount address when in Permit context. In other words, it MUST NOT 10 | // be possible to spoof Execution Context's onBehalfOfAccount (i.e. by using 11 | // call in a callback manner where authentication is not performed) and force 12 | // the EVC to rely on that spoofed address 13 | 14 | function getOnBehalfOfAccountCheckInEVC(env e, uint256 ec) returns address { 15 | assert e.msg.sender == currentContract; 16 | return ecGetOnBehalfOfAccount(e, ec); 17 | } 18 | 19 | rule onBehalfOfAccountOnlyInPermit(method f) filtered { f -> 20 | !isMustRevertFunction(f) && 21 | f.selector != sig:EthereumVaultConnectorHarness.getCurrentOnBehalfOfAccount(address).selector 22 | }{ 23 | env e; 24 | calldataarg args; 25 | // This will execute every function in EthereumVaultConnector while 26 | // replacing every call to ExecutionContext.getOnBehalfOfAccount with 27 | // the CVL function above. As a result, this will cause a violation 28 | // if this is ever called with e.msg.sender != currentContract 29 | f(e, args); 30 | // The following line is only here because rules must end in either 31 | // satisfy or assert. The useful part of this rule is in the summary 32 | // for getOnBehalfOfAccount 33 | satisfy true; 34 | } -------------------------------------------------------------------------------- /certora/specs/CER-2-Operator/CER-52-Operator-deauthorization.spec: -------------------------------------------------------------------------------- 1 | import "../utils/IsMustRevertFunction.spec"; 2 | import "CER-68-Operator-authorization.spec"; 3 | 4 | methods { 5 | function getOwnerOf(bytes19) external returns (address) envfree; 6 | function getOperator(bytes19, address) external returns (uint256) envfree; 7 | function getAddressPrefix(address) external returns (bytes19) envfree; 8 | function haveCommonOwner(address account, address otherAccount) external returns (bool) envfree; 9 | } 10 | 11 | // CER-52 Account Operator that is authorized to operate on behalf the Account MUST only be allowed to be deauthorized by: 12 | // - the Account Owner, or 13 | // - the Account Operator itself (one Account Operator MUST NOT be able to deauthorize the other Account Operator) 14 | rule operatorDeauthorization (method f) filtered { f -> 15 | !isMustRevertFunction(f) && 16 | f.selector != sig:batch(IEVC.BatchItem[] calldata).selector && 17 | f.selector != sig:call(address, address, uint256, bytes calldata).selector 18 | }{ 19 | env e; 20 | calldataarg args; 21 | address operator; 22 | bytes19 addressPrefix; 23 | address caller = actualCaller(e); 24 | address account; 25 | require addressPrefix == getAddressPrefix(account); 26 | requireInvariant OwnerIsFromPrefix(addressPrefix); 27 | address owner = haveCommonOwner(account, caller) ? caller : getAccountOwner(e, account); 28 | uint256 operatorBefore = getOperator(addressPrefix, operator); 29 | f(e,args); 30 | uint256 operatorAfter = getOperator(addressPrefix, operator); 31 | assert (operatorBefore != operatorAfter) => 32 | (caller == operator || caller == owner); 33 | } 34 | 35 | 36 | rule operatorDeauthorizationSetOperator { 37 | env e; 38 | 39 | bytes19 addressPrefix; 40 | address operator; 41 | uint256 operatorBitField; 42 | 43 | address caller = actualCaller(e); 44 | 45 | requireInvariant OwnerIsFromPrefix(addressPrefix); 46 | 47 | // call the setOperator() method giving 0 as the bit field to deauthorize 48 | setOperator(e, addressPrefix, operator, 0); 49 | // since the function did not revert the caller must be the owner 50 | assert caller != operator; 51 | assert caller == getOwnerOf(addressPrefix); 52 | assert getOperator(e, addressPrefix, operator) == 0; 53 | } 54 | 55 | rule operatorDeauthorizationSetAccountOperator() { 56 | env e; 57 | address account; 58 | address operator; 59 | 60 | address caller = actualCaller(e); 61 | address owner = haveCommonOwner(account, caller) ? caller : getAccountOwner(e, account); 62 | 63 | // call the setAccountOperator method giving false 64 | // as last parameter to deauthorize 65 | setAccountOperator(e, account, operator, false); 66 | 67 | 68 | // Since setAccountOperator did not revert, the actualCaller 69 | // must either be the owner or operator being deauthorized 70 | assert caller == owner || caller == operator; 71 | 72 | } 73 | -------------------------------------------------------------------------------- /certora/specs/CER-2-Operator/CER-54-Operator-owner.spec: -------------------------------------------------------------------------------- 1 | import "../utils/IsMustRevertFunction.spec"; 2 | import "CER-68-Operator-authorization.spec"; 3 | 4 | methods { 5 | function getOwnerOf(bytes19) external returns (address) envfree; 6 | function getOperator(bytes19, address) external returns (uint256) envfree; 7 | function getAddressPrefix(address) external returns (bytes19) envfree; 8 | function haveCommonOwner(address account, address otherAccount) external returns (bool) envfree; 9 | } 10 | 11 | // CER-54: Account Operator address MUST NOT belong to the Account Owner of the 12 | // Account for which the Operator being is authorized 13 | rule account_operator_owner() { 14 | env e; 15 | address account; 16 | address operator; 17 | 18 | // call the setAccountOperator method. 19 | setAccountOperator(e, account, operator, true); 20 | // Check that since this did not revert, the account and operator 21 | // must not have the same owner. 22 | assert !haveCommonOwner(account, operator); 23 | } 24 | 25 | rule setOperator_owner() { 26 | env e; 27 | 28 | bytes19 addressPrefix; 29 | address operator; 30 | uint256 operatorBitField; 31 | 32 | requireInvariant OwnerIsFromPrefix(addressPrefix); 33 | 34 | // call the setOperator() method. 35 | setOperator(e, addressPrefix, operator, operatorBitField); 36 | // For any address "addr" with prefix "addressPrefix" 37 | address addr; 38 | require getAddressPrefix(addr) == addressPrefix; 39 | // Since we did not revert, no such "addr" can have a common owner 40 | // with operator 41 | assert !haveCommonOwner(addr, operator); 42 | } -------------------------------------------------------------------------------- /certora/specs/CER-2-Operator/CER-62-Operator-setAccountOperator.spec: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Calling setAccountOperator does not affect the state for any operator 4 | * other than the target of the function call. 5 | */ 6 | methods { 7 | function getOperator(bytes19, address) external returns (uint256) envfree; 8 | function getAddressPrefix(address) external returns (bytes19) envfree; 9 | } 10 | 11 | rule setAccountOperatorSandboxed(address account, address operator, bool authorized) { 12 | address otherAccount; 13 | address otherOperator; 14 | env e; 15 | 16 | bytes19 addressPrefix = getAddressPrefix(otherAccount); 17 | // either otherAccount is from another prefix, or the operator is different 18 | require(getAddressPrefix(account) != addressPrefix || operator != otherOperator); 19 | 20 | uint256 operatorBefore = getOperator(addressPrefix, otherOperator); 21 | setAccountOperator(e, account, operator, authorized); 22 | uint256 operatorAfter = getOperator(addressPrefix, otherOperator); 23 | 24 | // the bitmask for a different account or different operator was not changed 25 | assert(operatorBefore == operatorAfter); 26 | } -------------------------------------------------------------------------------- /certora/specs/CER-2-Operator/CER-68-Operator-authorization.spec: -------------------------------------------------------------------------------- 1 | import "../utils/IsMustRevertFunction.spec"; 2 | import "../utils/ActualCaller.spec"; 3 | 4 | methods { 5 | function getOwnerOf(bytes19) external returns (address) envfree; 6 | function getOperator(bytes19, address) external returns (uint256) envfree; 7 | function getAddressPrefix(address) external returns (bytes19) envfree; 8 | function haveCommonOwner(address account, address otherAccount) external returns (bool) envfree; 9 | function isPhantomAccountContract(bytes19 addressPrefix) external returns (bool) envfree; 10 | } 11 | 12 | /** 13 | * Check that `setOperator(addressPrefix, ...)` can only be called if msg.sender 14 | * is the owner of the address prefix. Technically, we check that 15 | * - msg.sender is from the prefix itself and thus a plausible owner 16 | * - msg.sender is stored as the owner after the function call 17 | * - the owner before the call was either 0 (not set) or msg.sender already 18 | * - the bitset is set as it should be. 19 | */ 20 | rule onlyOwnerCanCallSetOperator() { 21 | env e; 22 | 23 | bytes19 addressPrefix; 24 | address operator; 25 | uint256 operatorBitField; 26 | 27 | address ownerBefore = getOwnerOf(addressPrefix); 28 | 29 | address caller = actualCaller(e); 30 | 31 | requireInvariant OwnerIsFromPrefix(addressPrefix); 32 | 33 | // call the setOperator() method. 34 | setOperator(e, addressPrefix, operator, operatorBitField); 35 | 36 | // sender is from the prefix itself and thus plausible to be the owner 37 | assert(getAddressPrefix(caller) == addressPrefix); 38 | // the owner before the call was either not set or actualCaller already 39 | assert(ownerBefore == 0 || ownerBefore == caller); 40 | // sender is stored as the owner of the address prefix 41 | assert(caller == getOwnerOf(addressPrefix)); 42 | // make sure the right bitfield was set 43 | assert(getOperator(addressPrefix, operator) == operatorBitField); 44 | } 45 | 46 | 47 | // a copy of the internal ownerLookup 48 | persistent ghost mapping(bytes19 => address) ownerLookupGhost { 49 | init_state axiom forall bytes19 prefix. ownerLookupGhost[prefix] == 0; 50 | } 51 | // makes sure the ownerLookupGhost is updated properly 52 | hook Sstore EthereumVaultConnectorHarness.ownerLookup[KEY bytes19 prefix].owner address value { 53 | ownerLookupGhost[prefix] = value; 54 | } 55 | // makes sure that reads from ownerLookup after havocs are correct 56 | hook Sload address value EthereumVaultConnectorHarness.ownerLookup[KEY bytes19 prefix].owner { 57 | require(ownerLookupGhost[prefix] == value); 58 | } 59 | 60 | // check that an owner of a prefix is always from that prefix 61 | invariant OwnerIsFromPrefix(bytes19 prefix) 62 | // Assume: In the inductive step, for the precondition, 63 | // we are assuming that the low-level `CALL`s that are 64 | // reachable in batch/call will not affect the state of ownerLookup 65 | ownerLookupGhost[prefix] == 0 || getAddressPrefix(ownerLookupGhost[prefix]) == prefix 66 | filtered { 67 | f -> !isMustRevertFunction(f) 68 | } 69 | 70 | /** 71 | * Checks a liveness property that the owner of an account 72 | * can succesfully set an operator (under a few assumptions 73 | * that are spelled out with the "require" statements). 74 | */ 75 | rule theOwnerCanCallSetOperator() { 76 | env e; 77 | 78 | bytes19 addressPrefix; 79 | address operator; 80 | uint256 operatorBitField; 81 | 82 | address owner = getOwnerOf(addressPrefix); 83 | requireInvariant OwnerIsFromPrefix(addressPrefix); 84 | 85 | // the actual caller (either msg.sender or the onBehalfOfAccount) 86 | address caller = actualCaller(e); 87 | 88 | // This is a permit self-call: 89 | if (e.msg.sender == currentContract) { 90 | // the owner is already set, not zero and not EVC 91 | require(owner == caller && owner != 0 && owner != currentContract); 92 | // the owner has the proper prefix 93 | require(getAddressPrefix(owner) == addressPrefix); 94 | // This is the normal case where the caller is msg.sender: 95 | } else { 96 | // just a regular call from msg.sender 97 | // msg.sender is from the proper prefix 98 | require(getAddressPrefix(e.msg.sender) == addressPrefix); 99 | // the owner is not set yet (zero) or identical to msg.sender 100 | require(owner == 0 || owner == e.msg.sender); 101 | // if the owner is not set yet, require that its phantom account is not a contract 102 | if (owner == 0) { 103 | require(!isPhantomAccountContract(addressPrefix)); 104 | } 105 | } 106 | 107 | // The function will revert if any of these assumptions do not hold: 108 | require(e.msg.value < nativeBalances[e.msg.sender]); 109 | require !(operator == currentContract); 110 | require !(haveCommonOwner(caller, operator)); 111 | 112 | // the operator can not be from the prefix either 113 | require(getAddressPrefix(operator) != getAddressPrefix(caller)); 114 | 115 | // the current bitfield must be different from what we try to set 116 | require(getOperator(addressPrefix, operator) != operatorBitField); 117 | 118 | // call the setOperator() method. 119 | setOperator@withrevert(e, addressPrefix, operator, operatorBitField); 120 | // check that it does not revert under these assumptions 121 | assert(!lastReverted); 122 | } 123 | 124 | // Only the owner of an account can set the operator of that account 125 | // if it is called with authenticated=true 126 | rule onlyOwnerOrOperatorCanCallSetAccountOperator() { 127 | env e; 128 | address account; 129 | address operator; 130 | 131 | address caller = actualCaller(e); 132 | 133 | // call the setAccountOperator method. 134 | setAccountOperator(e, account, operator, true); 135 | 136 | address owner = haveCommonOwner(account, caller) ? caller : getAccountOwner(e, account); 137 | 138 | // Since setAccountOperator did not revert, the actualCaller 139 | // must either be the owner 140 | assert(caller == owner); 141 | 142 | } -------------------------------------------------------------------------------- /certora/specs/CER-20-ChecksDeferrable/CER-22-ChecksDeferrable-controlCollateral.spec: -------------------------------------------------------------------------------- 1 | // CER-22: In ControlCollateral, only the enabled Controller of the specified 2 | // Account MUST be allowed to perform the operation on one of the enabled 3 | // Collaterals on behalf of the Account 4 | methods { 5 | function numOfController(address account) external returns (uint8) envfree; 6 | } 7 | 8 | rule controlCollateral_authorization { 9 | env e; 10 | address targetCollateral; 11 | address onBehalfOfAccount; 12 | uint256 value; 13 | bytes data; 14 | controlCollateral(e, targetCollateral, onBehalfOfAccount, value, data); 15 | assert e.msg.sender == getAccountController(e, onBehalfOfAccount); 16 | assert accountCollateralsContains(e, onBehalfOfAccount, targetCollateral); 17 | assert numOfController(onBehalfOfAccount) == 1; // See certora/harness/EthereumVaultConnectorHarness.sol 18 | } -------------------------------------------------------------------------------- /certora/specs/CER-20-ChecksDeferrable/CER-23-ChecksDeferrable-callbatch-callback.spec: -------------------------------------------------------------------------------- 1 | // CER-23: In Call and Batch, if the target is msg.sender, the caller MAY 2 | // specify any Account address to be set in the Execution Context's on behalf of 3 | // Account address. In that case, the authentication is not performed 4 | 5 | methods { 6 | function EthereumVaultConnector.authenticateCaller(address account, bool allowOperator, bool checkLockdownMode) internal returns (address) with (env e) => 7 | reachedAuthCaller(e, account, allowOperator); 8 | } 9 | 10 | persistent ghost bool didAuth; 11 | function reachedAuthCaller(env e, address addr, bool allowOperator) returns address { 12 | // set didAuth if authenticateCaller is ever reached 13 | didAuth = true; 14 | // this is not relevant to the rule 15 | // but mirrors the returned value of the summarized function 16 | return currentContract == e.msg.sender ? getExecutionContextOnBehalfOfAccount(e) : e.msg.sender; 17 | } 18 | 19 | rule call_authentication_skip { 20 | env e; 21 | address targetContract; 22 | address onBehalfOfAccount; 23 | uint256 value; 24 | bytes data; 25 | 26 | require e.msg.sender == targetContract; 27 | require !didAuth; 28 | call(e, targetContract, onBehalfOfAccount, value, data); 29 | assert !didAuth; 30 | } 31 | 32 | rule batch_authentication_skip { 33 | env e; 34 | IEVC.BatchItem[] items; 35 | require items.length == 1; 36 | require e.msg.sender == items[0].targetContract; 37 | require !didAuth; 38 | batch(e, items); 39 | assert !didAuth; 40 | } 41 | -------------------------------------------------------------------------------- /certora/specs/CER-27-ExecutionContext/CER-32-ExecutionContext-checksDeferred.spec: -------------------------------------------------------------------------------- 1 | // CER-32: Execution Context MUST keep track of whether the Checks are deferred 2 | // with a boolean flag. The flag MUST be set when a Checks-deferrable Call 3 | // starts and MUST be cleared at the end of it, but only when the flag was not 4 | // set before the call. 5 | import "../utils/IsChecksDeferredFunction.spec"; 6 | 7 | methods { 8 | function areChecksDeferred() external returns (bool) envfree; 9 | function getExecutionContextAreChecksDeferred() external returns (bool) envfree; 10 | } 11 | 12 | rule restoreChecksDeferred (method f) filtered { f -> 13 | isChecksDeferredFunction(f) 14 | }{ 15 | env e; 16 | calldataarg args; 17 | bool checksDeferredBefore = getExecutionContextAreChecksDeferred(); 18 | f(e, args); 19 | bool checksDeferredAfter = getExecutionContextAreChecksDeferred(); 20 | assert checksDeferredAfter == checksDeferredBefore; 21 | } -------------------------------------------------------------------------------- /certora/specs/CER-27-ExecutionContext/CER-33-ExecutionContext-onBehalfOfAccount.spec: -------------------------------------------------------------------------------- 1 | // CER-33: Execution Context MUST keep track of the Account on behalf of which 2 | // the current low-level external call is being performed. 3 | import "../utils/IsLowLevelCallFunction.spec"; 4 | 5 | methods { 6 | function getExecutionContextOnBehalfOfAccount() external returns (address) envfree; 7 | // There is another callback in checkAccountStatusInternal with 8 | // an external vault contract as the target. We want to exclude 9 | // this low-level CALL from these rules, and this is the point 10 | // of the following summary, which excludes the CALL but models 11 | // an arbitrary implementation of this function. Similar for 12 | // checkVaultStatusInternal 13 | // CER-76 checks properties of EVC's handling of checkAccountStatus 14 | function EthereumVaultConnector.requireAccountStatusCheckInternal(address account) internal => NONDET; 15 | // CER-77 checks properties of EVC's handling of checkVaultStatus 16 | function EthereumVaultConnector.requireVaultStatusCheckInternal(address vault) internal => NONDET; 17 | function EthereumVaultConnector.checkStatusAll(TransientStorage.SetType setType) internal => NONDET; 18 | } 19 | 20 | persistent ghost bool onBehalfOfCorrect; 21 | persistent ghost address savedOnBehalfOfAccount; 22 | 23 | hook CALL(uint g, address addr, uint value, uint argsOffset, uint argsLength, uint retOffset, uint retLength) uint rc 24 | { 25 | if(addr != currentContract) { 26 | onBehalfOfCorrect = onBehalfOfCorrect && 27 | (savedOnBehalfOfAccount == getExecutionContextOnBehalfOfAccount()); 28 | } 29 | } 30 | 31 | hook DELEGATECALL(uint g, address addr, uint argsOffset, uint argsLength, uint retOffset, uint retLength) uint rc 32 | { 33 | if(addr != currentContract) { 34 | onBehalfOfCorrect = onBehalfOfCorrect && 35 | (savedOnBehalfOfAccount == getExecutionContextOnBehalfOfAccount()); 36 | } 37 | } 38 | 39 | // Note: these rules are not parametric because we need 40 | // to initialize savedOnBehalfOfAccount to that parameter 41 | rule execution_context_tracks_account_for_call { 42 | env e; 43 | address targetContract; 44 | address onBehalfOfAccount; 45 | uint256 value; 46 | bytes data; 47 | // We prove this is true aside from in the context of permit 48 | // with CER-51 49 | require e.msg.sender != currentContract; 50 | // initialize ghosts 51 | require savedOnBehalfOfAccount == onBehalfOfAccount; 52 | require onBehalfOfCorrect; 53 | call(e, targetContract, onBehalfOfAccount, value, data); 54 | assert onBehalfOfCorrect; 55 | } 56 | 57 | rule execution_context_tracks_account_for_batch{ 58 | env e; 59 | IEVC.BatchItem[] items; 60 | // We prove this is true aside from in the context of permit 61 | // with CER-51 62 | require e.msg.sender != currentContract; 63 | // initialize ghosts 64 | require items.length == 1; 65 | require savedOnBehalfOfAccount == items[0].onBehalfOfAccount; 66 | require onBehalfOfCorrect; 67 | batch(e, items); 68 | assert onBehalfOfCorrect; 69 | } 70 | 71 | rule execution_context_tracks_account_for_controlCollateral { 72 | env e; 73 | address targetCollateral; 74 | address onBehalfOfAccount; 75 | uint256 value; 76 | bytes data; 77 | // We prove this is true aside from in the context of permit 78 | // with CER-51 79 | require e.msg.sender != currentContract; 80 | // initialize ghosts 81 | require savedOnBehalfOfAccount == onBehalfOfAccount; 82 | require onBehalfOfCorrect; 83 | controlCollateral(e, targetCollateral, onBehalfOfAccount, value, data); 84 | assert onBehalfOfCorrect; 85 | } -------------------------------------------------------------------------------- /certora/specs/CER-27-ExecutionContext/CER-38-ExecutionContext-restored.spec: -------------------------------------------------------------------------------- 1 | /** 2 | * Verification of: 3 | * Each external call, that the EVC performs, restores the value of the 4 | * execution context so that it’s equal to the value just before the external 5 | * call was performed. 6 | **/ 7 | 8 | import "../utils/IsMustRevertFunction.spec"; 9 | import "../utils/CallOpSanity.spec"; 10 | 11 | methods { 12 | function getRawExecutionContext() external returns (uint256) envfree; 13 | function getExecutionContextDefault() external returns (uint256) envfree; 14 | function areAccountStatusChecksEmpty() external returns (bool) envfree; 15 | function areVaultStatusChecksEmpty() external returns (bool) envfree; 16 | function getCurrentOnBehalfOfAccount(address) external returns (address,bool) envfree; 17 | } 18 | 19 | /** 20 | * Verify that any functions restores the execution context to whatever it was 21 | * beforehand. 22 | */ 23 | rule noFunctionChangesExecutionContext(method f) filtered {f -> !isMustRevertFunction(f)} 24 | { 25 | env e; 26 | calldataarg args; 27 | 28 | uint256 preEC = getRawExecutionContext(); 29 | f(e, args); 30 | assert(preEC == getRawExecutionContext()); 31 | } 32 | 33 | /** 34 | * Verify that after any function call, both account and 35 | * vault status checks are empty, and the execution context is set back to its 36 | * default value. 37 | * We ignore must-revert functions to avoid sanity issues. Additionally, we 38 | * ignore getCurrentOnBehalfOfAccount() as it always reverts. 39 | */ 40 | invariant topLevelFunctionDontChangeTransientStorage() 41 | areAccountStatusChecksEmpty() && areVaultStatusChecksEmpty() && 42 | getRawExecutionContext() == getExecutionContextDefault() 43 | filtered { f -> 44 | !isMustRevertFunction(f) && 45 | !f.isFallback && // certora plans to deprecate certora tool-generated function from sanity checks 46 | f.selector != sig:getCurrentOnBehalfOfAccount(address).selector 47 | } 48 | 49 | /** 50 | * Check that `getCurrentOnBehalfOfAccount` always reverts in case the invariant holds. 51 | * This justifies the filter applied to the invariant above. 52 | */ 53 | rule getCurrentOnBehalfOfAccountAlwaysReverts() { 54 | requireInvariant topLevelFunctionDontChangeTransientStorage(); 55 | env e; 56 | address controllerToCheck; 57 | getCurrentOnBehalfOfAccount@withrevert(controllerToCheck); 58 | assert(lastReverted); 59 | } 60 | -------------------------------------------------------------------------------- /certora/specs/CER-40-AccountStatusCheck/CER-326-checkStatusAll-called.spec: -------------------------------------------------------------------------------- 1 | methods { 2 | function EthereumVaultConnector.checkStatusAll(TransientStorage.SetType setType) internal => GhostCheckStatusAll(); 3 | } 4 | 5 | persistent ghost bool checkCalled; 6 | 7 | function GhostCheckStatusAll() { 8 | checkCalled = true; 9 | } 10 | 11 | // Passing: 12 | // https://prover.certora.com/output/65266/2523dd890b324c9cb6c1fcec767e030e?anonymousKey=5c7f3132f51538a96a5d8d4fb0de61f4ed892ccc 13 | 14 | rule checkStatusCalled_call { 15 | env e; 16 | require checkCalled == false; 17 | address targetContract; 18 | address onBehalfOfAccount; 19 | uint256 value; 20 | bytes data; 21 | // Assume the initial context did not defer checks. This 22 | // can be checked manually by looking at the contstructor 23 | require !getExecutionContextAreChecksDeferred(e); 24 | call(e, targetContract, onBehalfOfAccount, value, data); 25 | assert checkCalled; 26 | } 27 | 28 | rule checkStatusCalled_batch { 29 | env e; 30 | calldataarg args; 31 | require checkCalled == false; 32 | // Assume the initial context did not defer checks 33 | require !getExecutionContextAreChecksDeferred(e); 34 | batch(e, args); 35 | assert checkCalled; 36 | } 37 | 38 | // Permit 39 | // In permit, the targetContract on which the function is called 40 | // is hardcoded to be the EVC, so it gets converted into an ordinary 41 | // call op with a different caller. So the rule for call covers permit 42 | // as well. -------------------------------------------------------------------------------- /certora/specs/CER-40-AccountStatusCheck/CER-41-AccountStatusCheck-OnlyOne.spec: -------------------------------------------------------------------------------- 1 | methods { 2 | function numOfController(address account) external returns (uint8) envfree; 3 | } 4 | 5 | // CER-41: Account Status Check more than one controller. 6 | // If there is more than one Controller enabled for an Account at the time of 7 | // the Check, the Account Status MUST always be considered invalid 8 | rule account_status_only_one_controller { 9 | env e; 10 | address account; 11 | require numOfController(account) > 1; 12 | bool isValid = checkAccountStatus(e, account); 13 | assert !isValid; 14 | } -------------------------------------------------------------------------------- /certora/specs/CER-40-AccountStatusCheck/CER-42-AccountStatusCheck-NoController.spec: -------------------------------------------------------------------------------- 1 | methods { 2 | function numOfController(address account) external returns (uint8) envfree; 3 | } 4 | 5 | // CER-42: Account Status Check no controller 6 | // If there is no Controller enabled for an Account at the time of the Check, 7 | // the Account Status MUST always be considered valid. It includes disabling 8 | // the only enabled Controller before the Checks. 9 | rule account_status_no_controller { 10 | env e; 11 | address account; 12 | require numOfController(account) == 0; 13 | bool isValid = checkAccountStatus(e, account); 14 | assert isValid; 15 | } -------------------------------------------------------------------------------- /certora/specs/CER-44-VaultStatusCheck/CER-78-VaultStatusCheck-scheduling.spec: -------------------------------------------------------------------------------- 1 | import "../utils/IsMustRevertFunction.spec"; 2 | 3 | // CER-78: Vault Status Check scheduling 4 | // Only the Vault is allowed require the check for itself 5 | methods { 6 | function EthereumVaultConnector.requireVaultStatusCheckInternal(address vault) internal with (env e) => requireVaultStatusCheckOnlyCalledBySelf(e, vault); 7 | // For the calls that allow deferred checks (call, batch, 8 | // controlCollateral), we cannot prove that the 9 | // when the deferred checks are executed at the end of the deferred check 10 | // call that the set of vault addresses did not already contain 11 | // some vault address other than e.msg.sender. 12 | // These summaries exclude the deferred checks from the rule. 13 | // To strengthen this we also show that the vaultStatusChecks set can 14 | // only be modified for e.msg.sender 15 | function EthereumVaultConnector.checkStatusAll(TransientStorage.SetType setType) internal => NONDET; 16 | function EthereumVaultConnector.restoreExecutionContext(EthereumVaultConnectorHarness.EC ec) internal => NONDET; 17 | } 18 | 19 | // In all contexts where requireVaultStatusCheckInternal is called, 20 | // the caller should be the vault. 21 | persistent ghost bool onlyVault; 22 | function requireVaultStatusCheckOnlyCalledBySelf(env e, address vault) { 23 | onlyVault = onlyVault && e.msg.sender == vault; 24 | } 25 | 26 | rule vault_status_check_scheduling (method f) filtered { f -> 27 | !isMustRevertFunction(f) 28 | }{ 29 | env e; 30 | calldataarg args; 31 | // The point of this rule is to check that in all contexts in which 32 | // requireVaultStatusCheck is called, the vault is the message sender. 33 | // If requireVaultStatusCheck is reachable from the function called 34 | // it will modify the ghost if this is not true. 35 | // Assume initially the ghost variable is false 36 | require onlyVault; 37 | // Run the function which may reach requireVaultStatusCheck 38 | // invoking the summary. 39 | f(e, args); 40 | // Check: we have never run requireVaultStatusCheck with the wrong sender. 41 | assert onlyVault; 42 | } 43 | 44 | persistent ghost bool onlyInsertSender; 45 | persistent ghost address savedSender; 46 | hook Sstore currentContract.vaultStatusChecks.elements[INDEX uint256 _index].value address newValue (address oldValue) { 47 | onlyInsertSender = onlyInsertSender && (newValue == savedSender); 48 | } 49 | hook Sstore currentContract.vaultStatusChecks.firstElement address newValue { 50 | onlyInsertSender = onlyInsertSender && (newValue == savedSender); 51 | } 52 | 53 | rule insert_status_checks_only_by_sender (method f) filtered {f -> 54 | !isMustRevertFunction(f) && 55 | // The following is needed because of a limitation in our specifications. 56 | // To show that this holds also for forgiveVaultStatusCheck we would need 57 | // to rewrite the set library in a more modular way so we can import it 58 | // here. ForgiveVaultStatusCheck() directly calls remove on e.msg.sender 59 | // so by looking at the code this clearly can only affect e.msg.sender 60 | // and also only removes status checks rather than adding them. 61 | f.selector != sig:EthereumVaultConnectorHarness.forgiveVaultStatusCheck().selector 62 | }{ 63 | env e; 64 | calldataarg args; 65 | savedSender = e.msg.sender; 66 | require onlyInsertSender; 67 | f(e, args); 68 | assert onlyInsertSender; 69 | } -------------------------------------------------------------------------------- /certora/specs/CER-83-Set.spec: -------------------------------------------------------------------------------- 1 | /* Verification of `Set` library 2 | Full run: https://prover.certora.com/output/40726/c4fecbacb03045a28f210f7c0ca7c67a/?anonymousKey=1f4e421af7ce97274005b30761e9fb75a78d5aa0 3 | Mutation run: https://mutation-testing.certora.com/?id=8239ff0c-739c-4451-be5e-8b69336ccf7b&anonymousKey=c8d17ebd-bc1f-48e7-b201-a9e2bcf22d2b 4 | 5 | */ 6 | methods { 7 | function insert(address) external returns (bool) envfree; 8 | function remove(address) external returns (bool) envfree; 9 | function reorder(uint8, uint8) external envfree; 10 | function contains(address) external returns (bool) envfree; 11 | } 12 | 13 | 14 | definition get(uint8 index) returns address = 15 | (index==0 ? currentContract.setStorage.firstElement : currentContract.setStorage.elements[index].value); 16 | 17 | definition length() returns uint8 = currentContract.setStorage.numElements; 18 | 19 | 20 | /// @title Elements in set are unique 21 | invariant uniqueElements() 22 | forall uint8 i. forall uint8 j. 23 | (i < length() && j < length() && i != j => get(i) != get(j)) 24 | { 25 | preserved reorder(uint8 m, uint8 n) { 26 | //need to help the grounding a bit 27 | require uniqueElements_assumption(m,0); 28 | require uniqueElements_assumption(m,n); 29 | require uniqueElements_assumption(n,0); 30 | } 31 | } 32 | 33 | // Invariant uniqueElements is proven for all values, therefore it is safe to assume for two individual entries 34 | definition uniqueElements_assumption(uint8 i, uint8 j) returns bool = 35 | (i < length() && j < length() && i != j => get(i) != get(j)) ; 36 | 37 | 38 | 39 | /// @title The length of the set can change at most by 1 40 | rule setLengthChangedByOne(method f) { 41 | uint8 lengthBefore = length(); 42 | 43 | env e; 44 | calldataarg args; 45 | f(e, args); 46 | 47 | uint8 lengthAfter = length(); 48 | assert lengthAfter <= lengthBefore + 1; 49 | } 50 | 51 | 52 | /// @title Length is increased only by insert and decreased only by remove 53 | /// Meaning: 54 | /// - If the length increased then `insert` was called 55 | /// - If the length decreased then `remove` was called 56 | rule setLengthIncreaseDecrease(method f) { 57 | uint8 lengthBefore = length(); 58 | 59 | env e; 60 | calldataarg args; 61 | f(e, args); 62 | 63 | uint8 lengthAfter = length(); 64 | assert lengthAfter > lengthBefore => f.selector == sig:insert(address).selector; 65 | assert lengthAfter < lengthBefore => f.selector == sig:remove(address).selector; 66 | } 67 | 68 | 69 | 70 | // CER-86 Set insert: contains must return true if an element is present in 71 | // the set. (This is specified in combination with validSet/containsIntegrity) 72 | rule contained_if_inserted(address a) { 73 | env e; 74 | insert(a); 75 | assert(contains(a)); 76 | } 77 | 78 | // CER-91: set library MUST remove an element from the set if it's present 79 | rule not_contained_if_removed(address a) { 80 | env e; 81 | requireInvariant uniqueElements(); 82 | require uniqueElements_assumption(0,1); 83 | require uniqueElements_assumption(0,2); 84 | require uniqueElements_assumption(1,2); 85 | remove(a); 86 | assert(!contains(a)); 87 | } 88 | 89 | // CER-90: remove must return true if an element was successfully removed from 90 | // the set.remove must return false if an element was not removed from the set. 91 | // (In other words it returns true if and only if an element is removed). 92 | rule removed_iff_not_contained(address a) { 93 | env e; 94 | requireInvariant uniqueElements(); 95 | bool containsBefore = contains(a); 96 | bool succ = remove(a); 97 | assert(succ <=> containsBefore); 98 | } 99 | 100 | /** @title remove decreases the number of elements by one */ 101 | rule removed_then_length_decrease(address a) { 102 | env e; 103 | requireInvariant uniqueElements(); 104 | mathint lengthBefore = length(); 105 | bool succ = remove(a); 106 | assert(succ => length() == lengthBefore - 1); 107 | } 108 | 109 | use builtin rule sanity; -------------------------------------------------------------------------------- /certora/specs/misc/ExecutionContext.spec: -------------------------------------------------------------------------------- 1 | methods { 2 | function areChecksDeferred(ExecutionContextHarness.EC context) external returns (bool) envfree; 3 | function getOnBehalfOfAccount(ExecutionContextHarness.EC context) external returns (address) envfree; 4 | function setOnBehalfOfAccount(ExecutionContextHarness.EC context, address account) external returns (ExecutionContextHarness.EC) envfree; 5 | function areChecksInProgress(ExecutionContextHarness.EC context) external returns (bool) envfree; 6 | function setChecksInProgress(ExecutionContextHarness.EC context) external returns (ExecutionContextHarness.EC) envfree; 7 | function isOperatorAuthenticated(ExecutionContextHarness.EC context) external returns (bool) envfree; 8 | function setOperatorAuthenticated(ExecutionContextHarness.EC context) external returns (ExecutionContextHarness.EC) envfree; 9 | function clearOperatorAuthenticated(ExecutionContextHarness.EC context) external returns (ExecutionContextHarness.EC) envfree; 10 | function isSimulationInProgress(ExecutionContextHarness.EC context) external returns (bool) envfree; 11 | function setSimulationInProgress(ExecutionContextHarness.EC context) external returns (ExecutionContextHarness.EC) envfree; 12 | } 13 | 14 | /// check basic functionality of getOnBehalfOfAccount and setOnBehalfOfAccount 15 | rule check_on_behalf_of_account(uint ec, address adr) { 16 | address before = getOnBehalfOfAccount(ec); 17 | uint newec = setOnBehalfOfAccount(ec, adr); 18 | assert(getOnBehalfOfAccount(newec) == adr); 19 | uint resetec = setOnBehalfOfAccount(ec, before); 20 | assert(resetec == ec); 21 | } 22 | -------------------------------------------------------------------------------- /certora/specs/misc/MustRevertFunctions.spec: -------------------------------------------------------------------------------- 1 | import "../utils/IsMustRevertFunction.spec"; 2 | 3 | //Tests for a set of functions that have at least one input for which the function MUST not revert. 4 | rule nonRevertFunctions(method f) filtered {f -> !isMustRevertFunction(f)} { 5 | env e; calldataarg args; 6 | 7 | f(e,args); 8 | satisfy true, "The function always reverts."; 9 | } 10 | 11 | //Tests for the set of functions that MUST revert for all inputs. 12 | rule mustRevertFunctions(method f) filtered {f -> isMustRevertFunction(f)} { 13 | env e; calldataarg args; 14 | 15 | f@withrevert(e,args); 16 | assert lastReverted == true, "The function didn't revert for all input."; 17 | } 18 | -------------------------------------------------------------------------------- /certora/specs/misc/Permit-onBehalf-unSpoofable.spec: -------------------------------------------------------------------------------- 1 | // While in the context of a permit, it is not possible to spoof the 2 | // onBehalfOfAddress by making a callback that supplies a different argument for 3 | // onBehalfOfAddress. The permit will only use the onBehalfOfAddress value 4 | // stored in the context. 5 | 6 | // Show that if we are within the context of a permit 7 | // with onBehalfOfAccount == victim in the execution context, 8 | // and an adversary attempts to execute a call with a different 9 | // onbehalf of address, the call will revert 10 | rule cannot_spoof_onBehalfOfAccount_call() { 11 | env e; 12 | address victim; // the real onBehalfOfAccount 13 | address adversary; // a spoofed, nonzero onBehalfOfAccount 14 | require victim != adversary; 15 | require adversary != 0; 16 | 17 | // Setup the context of a permit with onBehalfOfAccount == victim 18 | require e.msg.sender == currentContract; 19 | require getExecutionContextOnBehalfOfAccount(e) == victim; 20 | 21 | // Attempt a call with a different nonzero onBehalfOfAccount 22 | uint256 value; 23 | bytes data; 24 | call@withrevert(e, currentContract, adversary, value, data); 25 | assert lastReverted; 26 | } 27 | 28 | rule cannot_spoof_onBehalfOfAccount_batch() { 29 | env e; 30 | address victim; // the real onBehalfOfAccount 31 | address adversary; // a spoofed, nonzero onBehalfOfAccount 32 | require victim != adversary; 33 | require adversary != 0; 34 | 35 | // Setup the context of a permit with onBehalfOfAccount == victim 36 | require e.msg.sender == currentContract; 37 | require getExecutionContextOnBehalfOfAccount(e) == victim; 38 | 39 | // Execute a batch() where one of the arguments is a 40 | // BatchItem with a spoofed onBehalfOfAccount 41 | IEVC.BatchItem[] items; 42 | uint256 i; 43 | require 0 <= i; 44 | require i < items.length; 45 | require items[i].onBehalfOfAccount == adversary; 46 | require items[i].targetContract == currentContract; 47 | batch@withrevert(e, items); 48 | assert lastReverted; 49 | 50 | } -------------------------------------------------------------------------------- /certora/specs/utils/ActualCaller.spec: -------------------------------------------------------------------------------- 1 | // if msg.sender is the currentContract, it means we are within permit() and 2 | // we need to use executionContext.getOnBehalfOfAccount() instead. 3 | function actualCaller(env e) returns address { 4 | if(e.msg.sender == currentContract) { 5 | return getExecutionContextOnBehalfOfAccount(e); 6 | } else { 7 | return e.msg.sender; 8 | } 9 | } -------------------------------------------------------------------------------- /certora/specs/utils/CallOpSanity.spec: -------------------------------------------------------------------------------- 1 | /** 2 | * This file collects a few basic checks concerning the sanity of calls out of 3 | * the EVC contract, making sure they don't give other code access to our 4 | * storage. There are generally two ways to do this, using `callcode` or 5 | * `delegatecall`. Thus we check that: 6 | * - `callcode` should not be used 7 | * - `delegatecall` should only call directly into `currentContract` 8 | * 9 | * This implies that whenever we call other code (via `call` or `staticcall`) 10 | * that code has no (write) access to our storage. Furthermore, any reentrant 11 | * call (with write access) comes with `msg.sender != currentContract`. 12 | */ 13 | 14 | hook CALLCODE(uint g, address addr, uint value, uint argsOffset, uint argsLength, uint retOffset, uint retLength) uint rc 15 | { 16 | assert(executingContract != currentContract, 17 | "we should not use `callcode`" 18 | ); 19 | } 20 | 21 | hook DELEGATECALL(uint g, address addr, uint argsOffset, uint argsLength, uint retOffset, uint retLength) uint rc 22 | { 23 | assert(executingContract != currentContract || addr == currentContract, 24 | "we should only `delegatecall` into ourselves" 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /certora/specs/utils/IsChecksDeferredFunction.spec: -------------------------------------------------------------------------------- 1 | /** 2 | * This is all the checksDeferred functions (call, controlCollateral, batch) 3 | * aside from batchRevert which always reverts 4 | */ 5 | definition isChecksDeferredFunction(method f) returns bool = 6 | f.selector == sig:EthereumVaultConnectorHarness.call( 7 | address,address,uint256,bytes).selector || 8 | f.selector == sig:EthereumVaultConnectorHarness.batch( 9 | IEVC.BatchItem[]).selector || 10 | f.selector == sig:EthereumVaultConnectorHarness.controlCollateral( 11 | address, address, uint256, bytes).selector; 12 | -------------------------------------------------------------------------------- /certora/specs/utils/IsLowLevelCallFunction.spec: -------------------------------------------------------------------------------- 1 | /** 2 | * This is all the functions with low-level call operations 3 | * that do not always revert (e.g. batchRevert is omitted) 4 | */ 5 | definition isLowLevelCallFunction(method f) returns bool = 6 | f.selector == sig:EthereumVaultConnectorHarness.permit( 7 | address,address,uint256,uint256,uint256,uint256,bytes,bytes).selector || 8 | f.selector == sig:EthereumVaultConnectorHarness.call( 9 | address,address,uint256,bytes).selector || 10 | f.selector == sig:EthereumVaultConnectorHarness.batch( 11 | IEVC.BatchItem[]).selector || 12 | f.selector == sig:EthereumVaultConnectorHarness.controlCollateral( 13 | address, address, uint256, bytes).selector; 14 | -------------------------------------------------------------------------------- /certora/specs/utils/IsMustRevertFunction.spec: -------------------------------------------------------------------------------- 1 | /** 2 | * This utility function identifies methods that are expected to always revert. 3 | * These functions are `batchSimulation` and `batchRevert`. 4 | * They usually need to be excluded from explicit sanity rules, as well as 5 | * generic rules or invariants if automatic sanity checks are enabled. 6 | */ 7 | definition isMustRevertFunction(method f) returns bool = 8 | f.selector == sig:EthereumVaultConnectorHarness.batchRevert(IEVC.BatchItem[]).selector; 9 | -------------------------------------------------------------------------------- /docs/diagrams/Direct Vault Interaction Sequence.md: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | sequenceDiagram 3 | actor User 4 | Note right of User: Dotted lines mean optional 5 | participant Any Vault 6 | participant EVC 7 | participant Controller Vault 8 | participant Price Oracle 9 | 10 | User->>Any Vault: operation 11 | Any Vault->>EVC: call(operation) 12 | EVC->>EVC: set the execution context 13 | EVC->>Any Vault: operation 14 | Any Vault->>EVC: getCurrentOnBehalfOfAccount(address(vault)/address(0)) 15 | Any Vault-->>Any Vault: vault snapshot 16 | Any Vault->>Any Vault: operation logic 17 | Any Vault->>EVC: requireAccountStatusCheck(account) 18 | Any Vault->>EVC: requireVaultStatusCheck() 19 | 20 | critical 21 | EVC->>Controller Vault: checkAccountStatus(account, collaterals) 22 | Controller Vault->>Controller Vault: is msg.sender EVC? 23 | Controller Vault->>EVC: areChecksInProgress() 24 | Controller Vault-->>Any Vault: balanceOf() 25 | Controller Vault-->>Price Oracle: getQuote() 26 | Controller Vault->>Controller Vault: determine account's liquidity 27 | 28 | EVC->>Any Vault: checkVaultStatus() 29 | Any Vault->>Any Vault: is msg.sender EVC? 30 | Any Vault->>EVC: areChecksInProgress() 31 | Any Vault->>Any Vault: determine vault's health 32 | end 33 | 34 | EVC->>EVC: clear the execution context 35 | ``` -------------------------------------------------------------------------------- /docs/diagrams/EVC Batch Vault Interaction Sequence.md: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | sequenceDiagram 3 | actor User/Operator 4 | Note right of User/Operator: Dotted lines mean optional 5 | participant EVC 6 | participant Any Vault 7 | participant Controller Vault 8 | participant Price Oracle 9 | 10 | User/Operator->>EVC: batch([operation 1, operation 2, ...]) 11 | 12 | loop 13 | EVC->>EVC: set the execution context 14 | EVC->>Any Vault: operation i 15 | Any Vault->>EVC: getCurrentOnBehalfOfAccount(address(vault)/address(0)) 16 | Any Vault-->>Any Vault: vault snapshot 17 | Any Vault->>Any Vault: operation i logic 18 | Any Vault->>EVC: requireAccountStatusCheck(account) 19 | Any Vault->>EVC: requireVaultStatusCheck() 20 | end 21 | 22 | critical 23 | EVC->>Controller Vault: checkAccountStatus(account, collaterals) 24 | Controller Vault->>Controller Vault: is msg.sender EVC? 25 | Controller Vault->>EVC: areChecksInProgress() 26 | Controller Vault-->>Any Vault: balanceOf() 27 | Controller Vault-->>Price Oracle: getQuote() 28 | Controller Vault->>Controller Vault: determine account's liquidity 29 | 30 | EVC->>Any Vault: checkVaultStatus() 31 | Any Vault->>Any Vault: is msg.sender EVC? 32 | Any Vault->>EVC: areChecksInProgress() 33 | Any Vault->>Any Vault: determine vault's health 34 | end 35 | 36 | EVC->>EVC: clear the execution context 37 | ``` -------------------------------------------------------------------------------- /docs/diagrams/EVC Call Vault Interaction Sequence.md: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | sequenceDiagram 3 | actor User/Operator 4 | Note right of User/Operator: Dotted lines mean optional 5 | participant EVC 6 | participant Any Vault 7 | participant Controller Vault 8 | participant Price Oracle 9 | 10 | User/Operator->>EVC: call(operation) 11 | EVC->>Any Vault: operation 12 | Any Vault->>EVC: getCurrentOnBehalfOfAccount(true/false) 13 | Any Vault-->>Any Vault: vault snapshot 14 | Any Vault->>Any Vault: operation logic 15 | Any Vault->>EVC: requireAccountStatusCheck(account) 16 | Any Vault->>EVC: requireVaultStatusCheck() 17 | 18 | critical 19 | EVC->>Controller Vault: checkAccountStatus(account, collaterals) 20 | Controller Vault->>Controller Vault: is msg.sender EVC? 21 | Controller Vault->>EVC: areChecksInProgress() 22 | Controller Vault-->>Any Vault: balanceOf() 23 | Controller Vault-->>Price Oracle: getQuote() 24 | Controller Vault->>Controller Vault: determine account's liquidity 25 | 26 | EVC->>Any Vault: checkVaultStatus() 27 | Any Vault->>Any Vault: is msg.sender EVC? 28 | Any Vault->>EVC: areChecksInProgress() 29 | Any Vault->>Any Vault: determine vault's health 30 | end 31 | 32 | EVC->>EVC: clear the execution context 33 | ``` -------------------------------------------------------------------------------- /docs/diagrams/EVC Permit Vault Interaction Sequence.md: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | sequenceDiagram 3 | actor Signature Holder 4 | Note right of Signature Holder: Dotted lines mean optional 5 | participant EVC 6 | participant Any Vault 7 | participant Controller Vault 8 | participant Price Oracle 9 | 10 | Signature Holder->>EVC: permit(signer, signature, EVC calldata) 11 | EVC->>EVC: signature verification 12 | EVC->>EVC: set the execution context 13 | EVC->>EVC: address(this).call(EVC calldata) 14 | 15 | loop 16 | EVC->>Any Vault: operation 17 | Any Vault->>EVC: getCurrentOnBehalfOfAccount(address(vault)/address(0)) 18 | Any Vault-->>Any Vault: vault snapshot 19 | Any Vault->>Any Vault: operation logic 20 | Any Vault->>EVC: requireAccountStatusCheck(account) 21 | Any Vault->>EVC: requireVaultStatusCheck() 22 | end 23 | 24 | critical 25 | EVC->>Controller Vault: checkAccountStatus(account, collaterals) 26 | Controller Vault->>Controller Vault: is msg.sender EVC? 27 | Controller Vault->>EVC: areChecksInProgress() 28 | Controller Vault-->>Any Vault: balanceOf() 29 | Controller Vault-->>Price Oracle: getQuote() 30 | Controller Vault->>Controller Vault: determine account's liquidity 31 | 32 | EVC->>Any Vault: checkVaultStatus() 33 | Any Vault->>Any Vault: is msg.sender EVC? 34 | Any Vault->>EVC: areChecksInProgress() 35 | Any Vault->>Any Vault: determine vault's health 36 | end 37 | 38 | EVC->>EVC: clear the execution context 39 | ``` -------------------------------------------------------------------------------- /docs/diagrams/How EVC Works Example Sequence.md: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | sequenceDiagram 3 | actor User/Operator 4 | participant EVC 5 | participant Controller Vault 6 | 7 | participant Any Vault 8 | participant Price Oracle 9 | 10 | User/Operator->>EVC: enableCollateral(collateral) 11 | User/Operator->>EVC: enableController(controller) 12 | User/Operator->>EVC: call(controller, account, borrow) 13 | EVC->>EVC: set the execution context 14 | EVC->>Controller Vault: borrow 15 | Controller Vault->>EVC: getCurrentOnBehalfOfAccount(address(vault)) 16 | Controller Vault->>Controller Vault: vault snapshot 17 | Controller Vault->>Controller Vault: borrow logic 18 | Controller Vault->>EVC: requireAccountStatusCheck(account) 19 | Controller Vault->>EVC: requireVaultStatusCheck() 20 | 21 | critical 22 | EVC->>Controller Vault: checkAccountStatus(account, collaterals) 23 | Controller Vault->>Controller Vault: is msg.sender EVC? 24 | Controller Vault->>EVC: areChecksInProgress() 25 | Controller Vault->>Any Vault: balanceOf() 26 | Controller Vault->>Price Oracle: getQuote() 27 | Controller Vault->>Controller Vault: determine account's liquidity 28 | 29 | EVC->>Controller Vault: checkVaultStatus() 30 | Controller Vault->>Controller Vault: is msg.sender EVC? 31 | Controller Vault->>EVC: areChecksInProgress() 32 | Controller Vault->>Controller Vault: determine vault's health 33 | end 34 | 35 | EVC->>EVC: clear the execution context 36 | ``` -------------------------------------------------------------------------------- /docs/diagrams/Liquidation Interaction Sequence.md: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | sequenceDiagram 3 | actor Violator 4 | Note right of Violator: Dotted lines mean optional 5 | actor Liquidator 6 | participant Controller Vault 7 | participant EVC 8 | participant Collateral Vault 9 | participant Price Oracle 10 | 11 | Liquidator->>Controller Vault: liquidate(violator, collateral vault) 12 | Controller Vault->>EVC: call(liquidate(violator, collateral vault)) 13 | EVC->>EVC: set the execution context 14 | EVC->>Controller Vault: liquidate(violator, collateral vault) 15 | 16 | Controller Vault->>EVC: liquidator = getCurrentOnBehalfOfAccount(address(vault)) 17 | Controller Vault->>Controller Vault: is liquidator liquidating itself? 18 | Controller Vault->>EVC: isControllerEnabled(violator, Controller Vault) 19 | Controller Vault->>Controller Vault: controller must be enabled 20 | Controller Vault->>EVC: isAccountStatusCheckDeferred(violator) 21 | Controller Vault->>Controller Vault: account status check cannot be deferred 22 | Controller Vault-->>Controller Vault: is the requested collateral accepted and trusted? 23 | Controller Vault->>Controller Vault: is the violator indeed in violation? 24 | Controller Vault-->>Controller Vault: vault snapshot 25 | Controller Vault->>Controller Vault: liquidation logic 26 | Controller Vault->>Controller Vault: transfer the liability from the violator to the liquidator 27 | Controller Vault-->>Controller Vault: if Controller Vault == Collateral Vault, seize violator's collateral 28 | 29 | critical 30 | Controller Vault-->>EVC: if Controller Vault != Collateral Vault, controlCollateral(collateral vault, violator, transfer(liquidator, collateral amount)) 31 | EVC->>Collateral Vault: transfer(liquidator, collateral amount) 32 | Collateral Vault->>EVC: getCurrentOnBehalfOfAccount(address(0)) 33 | Collateral Vault-->>Collateral Vault: vault snapshot 34 | Collateral Vault->>Collateral Vault: transfer logic 35 | Collateral Vault->>EVC: requireAccountStatusCheck(violator) 36 | Collateral Vault->>EVC: requireVaultStatusCheck() 37 | end 38 | 39 | Controller Vault-->>EVC: if collateral trusted or action can be verified, forgiveAccountStatusCheck(violator) 40 | Controller Vault->>EVC: requireAccountStatusCheck(liquidator) 41 | Controller Vault->>EVC: requireVaultStatusCheck() 42 | 43 | critical 44 | opt if check not forgiven 45 | EVC->>Controller Vault: checkAccountStatus(violator, collaterals) 46 | Controller Vault->>Controller Vault: is msg.sender EVC? 47 | Controller Vault->>EVC: areChecksInProgress() 48 | Controller Vault-->>Price Oracle: getQuote() 49 | Controller Vault->>Controller Vault: determine violator's liquidity 50 | end 51 | 52 | EVC->>Controller Vault: checkAccountStatus(liquidator, collaterals) 53 | Controller Vault->>Controller Vault: is msg.sender EVC? 54 | Controller Vault->>EVC: areChecksInProgress() 55 | Controller Vault-->>Collateral Vault: balanceOf() 56 | Controller Vault-->>Price Oracle: getQuote() 57 | Controller Vault->>Controller Vault: determine liquidator's liquidity 58 | 59 | EVC->>Collateral Vault: checkVaultStatus() 60 | Collateral Vault->>Collateral Vault: is msg.sender EVC? 61 | Collateral Vault->>EVC: areChecksInProgress() 62 | Collateral Vault->>Collateral Vault: determine vault's health 63 | 64 | EVC->>Controller Vault: checkVaultStatus() 65 | Controller Vault->>Controller Vault: is msg.sender EVC? 66 | Controller Vault->>EVC: areChecksInProgress() 67 | Controller Vault->>Controller Vault: determine vault's health 68 | end 69 | 70 | EVC->>EVC: clear the execution context 71 | ``` -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | solc = "0.8.24" 6 | optimizer_runs = 10000 7 | ignored_error_codes = ["unreachable"] 8 | gas_reports = [ 9 | "EthereumVaultConnector" 10 | ] 11 | remappings = [ 12 | "forge-std/=lib/forge-std/src/", 13 | "openzeppelin/=lib/openzeppelin-contracts/contracts" 14 | ] 15 | 16 | 17 | [profile.default.fuzz] 18 | max_test_rejects = 1_000_000 19 | seed = "0xee1d0f7d9556539a9c0e26aed5e63556" 20 | runs = 1000 21 | 22 | [profile.default.invariant] 23 | call_override = false # Override unsafe external calls to perform reentrancy checks 24 | depth = 20 25 | runs = 1000 26 | 27 | 28 | [profile.default.fmt] 29 | line_length = 120 30 | tab_width = 4 31 | bracket_spacing = false 32 | int_types = "long" 33 | multiline_func_header = "params_first" 34 | quote_style = "double" 35 | number_underscore = "preserve" 36 | override_spacing = true 37 | wrap_comments = true 38 | ignore = [ 39 | "test/evc/EthereumVaultConnectorScribble.sol" 40 | ] 41 | 42 | 43 | [profile.default.doc] 44 | out = "forgedoc" 45 | title = "Ethereum Vault Connector (EVC)" 46 | repository = "https://github.com/euler-xyz/ethereum-vault-connector" 47 | 48 | 49 | [profile.smt.model_checker] 50 | engine = "chc" 51 | timeout = 100_000 52 | contracts = {"./src/EthereumVaultConnector.sol" = ["EthereumVaultConnector"]} 53 | invariants = ["contract", "reentrancy"] 54 | targets = [ 55 | "assert", 56 | "constantCondition", 57 | "divByZero", 58 | "outOfBounds", 59 | "overflow", 60 | "underflow", 61 | ] 62 | -------------------------------------------------------------------------------- /mythril.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "optimizer": { 3 | "enabled": true, 4 | "runs": 10000 5 | }, 6 | "remappings": [ 7 | "forge-std/=lib/forge-std/src/", 8 | "openzeppelin/=lib/openzeppelin-contracts/contracts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethereum-vault-connector", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "https://github.com/euler-xyz/ethereum-vault-connector.git", 6 | "author": "Euler Labs", 7 | "license": "GPL-2.0-or-later", 8 | "scripts": {}, 9 | "dependencies": {}, 10 | "devDependencies": {} 11 | } 12 | -------------------------------------------------------------------------------- /slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "filter_paths": "lib", 3 | "solc_remaps": [ 4 | "forge-std/=lib/forge-std/src/", 5 | "openzeppelin/=lib/openzeppelin-contracts/contracts" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/Errors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {IEVC} from "./interfaces/IEthereumVaultConnector.sol"; 6 | 7 | /// @title Errors 8 | /// @custom:security-contact security@euler.xyz 9 | /// @author Euler Labs (https://www.eulerlabs.com/) 10 | /// @notice This contract implements the error messages for the Ethereum Vault Connector. 11 | contract Errors { 12 | /// @notice Error for when caller is not authorized to perform an operation. 13 | error EVC_NotAuthorized(); 14 | /// @notice Error for when no account has been authenticated to act on behalf of. 15 | error EVC_OnBehalfOfAccountNotAuthenticated(); 16 | /// @notice Error for when an operator's to be set is no different from the current one. 17 | error EVC_InvalidOperatorStatus(); 18 | /// @notice Error for when a nonce is invalid or already used. 19 | error EVC_InvalidNonce(); 20 | /// @notice Error for when an address parameter passed is invalid. 21 | error EVC_InvalidAddress(); 22 | /// @notice Error for when a timestamp parameter passed is expired. 23 | error EVC_InvalidTimestamp(); 24 | /// @notice Error for when a value parameter passed is invalid or exceeds current balance. 25 | error EVC_InvalidValue(); 26 | /// @notice Error for when data parameter passed is empty. 27 | error EVC_InvalidData(); 28 | /// @notice Error for when an action is prohibited due to the lockdown mode. 29 | error EVC_LockdownMode(); 30 | /// @notice Error for when permit execution is prohibited due to the permit disabled mode. 31 | error EVC_PermitDisabledMode(); 32 | /// @notice Error for when checks are in progress and reentrancy is not allowed. 33 | error EVC_ChecksReentrancy(); 34 | /// @notice Error for when control collateral is in progress and reentrancy is not allowed. 35 | error EVC_ControlCollateralReentrancy(); 36 | /// @notice Error for when there is a different number of controllers enabled than expected. 37 | error EVC_ControllerViolation(); 38 | /// @notice Error for when a simulation batch is nested within another simulation batch. 39 | error EVC_SimulationBatchNested(); 40 | /// @notice Auxiliary error to pass simulation batch results. 41 | error EVC_RevertedBatchResult( 42 | IEVC.BatchItemResult[] batchItemsResult, 43 | IEVC.StatusCheckResult[] accountsStatusResult, 44 | IEVC.StatusCheckResult[] vaultsStatusResult 45 | ); 46 | /// @notice Panic error for when simulation does not behave as expected. Should never be observed. 47 | error EVC_BatchPanic(); 48 | /// @notice Error for when an empty or undefined error is thrown. 49 | error EVC_EmptyError(); 50 | } 51 | -------------------------------------------------------------------------------- /src/Events.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /// @title Events 6 | /// @custom:security-contact security@euler.xyz 7 | /// @author Euler Labs (https://www.eulerlabs.com/) 8 | /// @notice This contract implements the events for the Ethereum Vault Connector. 9 | contract Events { 10 | /// @notice Emitted when an owner is registered for an address prefix. 11 | /// @param addressPrefix The address prefix for which the owner is registered. 12 | /// @param owner The address of the owner registered. 13 | event OwnerRegistered(bytes19 indexed addressPrefix, address indexed owner); 14 | 15 | /// @notice Emitted when the lockdown mode status is changed for an address prefix. 16 | /// @param addressPrefix The address prefix for which the lockdown mode status is changed. 17 | /// @param enabled True if the lockdown mode is enabled, false otherwise. 18 | event LockdownModeStatus(bytes19 indexed addressPrefix, bool enabled); 19 | 20 | /// @notice Emitted when the permit disabled mode status is changed for an address prefix. 21 | /// @param addressPrefix The address prefix for which the permit disabled mode status is changed. 22 | /// @param enabled True if the permit disabled mode is enabled, false otherwise. 23 | event PermitDisabledModeStatus(bytes19 indexed addressPrefix, bool enabled); 24 | 25 | /// @notice Emitted when the nonce status is updated for a given address prefix and nonce namespace. 26 | /// @param addressPrefix The prefix of the address for which the nonce status is updated. 27 | /// @param nonceNamespace The namespace of the nonce being updated. 28 | /// @param oldNonce The previous nonce value before the update. 29 | /// @param newNonce The new nonce value after the update. 30 | event NonceStatus( 31 | bytes19 indexed addressPrefix, uint256 indexed nonceNamespace, uint256 oldNonce, uint256 newNonce 32 | ); 33 | 34 | /// @notice Emitted when a nonce is used for an address prefix and nonce namespace as part of permit execution. 35 | /// @param addressPrefix The address prefix for which the nonce is used. 36 | /// @param nonceNamespace The namespace of the nonce used. 37 | /// @param nonce The nonce that was used. 38 | event NonceUsed(bytes19 indexed addressPrefix, uint256 indexed nonceNamespace, uint256 nonce); 39 | 40 | /// @notice Emitted when the operator status is changed for an address prefix. 41 | /// @param addressPrefix The address prefix for which the operator status is changed. 42 | /// @param operator The address of the operator. 43 | /// @param accountOperatorAuthorized The new authorization bitfield of the operator. 44 | event OperatorStatus(bytes19 indexed addressPrefix, address indexed operator, uint256 accountOperatorAuthorized); 45 | 46 | /// @notice Emitted when the collateral status is changed for an account. 47 | /// @param account The account for which the collateral status is changed. 48 | /// @param collateral The address of the collateral. 49 | /// @param enabled True if the collateral is enabled, false otherwise. 50 | event CollateralStatus(address indexed account, address indexed collateral, bool enabled); 51 | 52 | /// @notice Emitted when the controller status is changed for an account. 53 | /// @param account The account for which the controller status is changed. 54 | /// @param controller The address of the controller. 55 | /// @param enabled True if the controller is enabled, false otherwise. 56 | event ControllerStatus(address indexed account, address indexed controller, bool enabled); 57 | 58 | /// @notice Emitted when an external call is made through the EVC. 59 | /// @param caller The address of the caller. 60 | /// @param onBehalfOfAddressPrefix The address prefix of the account on behalf of which the call is made. 61 | /// @param onBehalfOfAccount The account on behalf of which the call is made. 62 | /// @param targetContract The target contract of the call. 63 | /// @param selector The selector of the function called on the target contract. 64 | event CallWithContext( 65 | address indexed caller, 66 | bytes19 indexed onBehalfOfAddressPrefix, 67 | address onBehalfOfAccount, 68 | address indexed targetContract, 69 | bytes4 selector 70 | ); 71 | 72 | /// @notice Emitted when an account status check is performed. 73 | /// @param account The account for which the status check is performed. 74 | /// @param controller The controller performing the status check. 75 | event AccountStatusCheck(address indexed account, address indexed controller); 76 | 77 | /// @notice Emitted when a vault status check is performed. 78 | /// @param vault The vault for which the status check is performed. 79 | event VaultStatusCheck(address indexed vault); 80 | } 81 | -------------------------------------------------------------------------------- /src/ExecutionContext.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | type EC is uint256; 6 | 7 | /// @title ExecutionContext 8 | /// @custom:security-contact security@euler.xyz 9 | /// @author Euler Labs (https://www.eulerlabs.com/) 10 | /// @notice This library provides functions for managing the execution context in the Ethereum Vault Connector. 11 | /// @dev The execution context is a bit field that stores the following information: 12 | /// @dev - on behalf of account - an account on behalf of which the currently executed operation is being performed 13 | /// @dev - checks deferred flag - used to indicate whether checks are deferred 14 | /// @dev - checks in progress flag - used to indicate that the account/vault status checks are in progress. This flag is 15 | /// used to prevent re-entrancy. 16 | /// @dev - control collateral in progress flag - used to indicate that the control collateral is in progress. This flag 17 | /// is used to prevent re-entrancy. 18 | /// @dev - operator authenticated flag - used to indicate that the currently executed operation is being performed by 19 | /// the account operator 20 | /// @dev - simulation flag - used to indicate that the currently executed batch call is a simulation 21 | /// @dev - stamp - dummy value for optimization purposes 22 | library ExecutionContext { 23 | uint256 internal constant ON_BEHALF_OF_ACCOUNT_MASK = 24 | 0x000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; 25 | uint256 internal constant CHECKS_DEFERRED_MASK = 0x0000000000000000000000FF0000000000000000000000000000000000000000; 26 | uint256 internal constant CHECKS_IN_PROGRESS_MASK = 27 | 0x00000000000000000000FF000000000000000000000000000000000000000000; 28 | uint256 internal constant CONTROL_COLLATERAL_IN_PROGRESS_LOCK_MASK = 29 | 0x000000000000000000FF00000000000000000000000000000000000000000000; 30 | uint256 internal constant OPERATOR_AUTHENTICATED_MASK = 31 | 0x0000000000000000FF0000000000000000000000000000000000000000000000; 32 | uint256 internal constant SIMULATION_MASK = 0x00000000000000FF000000000000000000000000000000000000000000000000; 33 | uint256 internal constant STAMP_OFFSET = 200; 34 | 35 | // None of the functions below modifies the state. All the functions operate on the copy 36 | // of the execution context and return its modified value as a result. In order to update 37 | // one should use the result of the function call as a new execution context value. 38 | 39 | function getOnBehalfOfAccount(EC self) internal pure returns (address result) { 40 | result = address(uint160(EC.unwrap(self) & ON_BEHALF_OF_ACCOUNT_MASK)); 41 | } 42 | 43 | function setOnBehalfOfAccount(EC self, address account) internal pure returns (EC result) { 44 | result = EC.wrap((EC.unwrap(self) & ~ON_BEHALF_OF_ACCOUNT_MASK) | uint160(account)); 45 | } 46 | 47 | function areChecksDeferred(EC self) internal pure returns (bool result) { 48 | result = EC.unwrap(self) & CHECKS_DEFERRED_MASK != 0; 49 | } 50 | 51 | function setChecksDeferred(EC self) internal pure returns (EC result) { 52 | result = EC.wrap(EC.unwrap(self) | CHECKS_DEFERRED_MASK); 53 | } 54 | 55 | function areChecksInProgress(EC self) internal pure returns (bool result) { 56 | result = EC.unwrap(self) & CHECKS_IN_PROGRESS_MASK != 0; 57 | } 58 | 59 | function setChecksInProgress(EC self) internal pure returns (EC result) { 60 | result = EC.wrap(EC.unwrap(self) | CHECKS_IN_PROGRESS_MASK); 61 | } 62 | 63 | function isControlCollateralInProgress(EC self) internal pure returns (bool result) { 64 | result = EC.unwrap(self) & CONTROL_COLLATERAL_IN_PROGRESS_LOCK_MASK != 0; 65 | } 66 | 67 | function setControlCollateralInProgress(EC self) internal pure returns (EC result) { 68 | result = EC.wrap(EC.unwrap(self) | CONTROL_COLLATERAL_IN_PROGRESS_LOCK_MASK); 69 | } 70 | 71 | function isOperatorAuthenticated(EC self) internal pure returns (bool result) { 72 | result = EC.unwrap(self) & OPERATOR_AUTHENTICATED_MASK != 0; 73 | } 74 | 75 | function setOperatorAuthenticated(EC self) internal pure returns (EC result) { 76 | result = EC.wrap(EC.unwrap(self) | OPERATOR_AUTHENTICATED_MASK); 77 | } 78 | 79 | function clearOperatorAuthenticated(EC self) internal pure returns (EC result) { 80 | result = EC.wrap(EC.unwrap(self) & ~OPERATOR_AUTHENTICATED_MASK); 81 | } 82 | 83 | function isSimulationInProgress(EC self) internal pure returns (bool result) { 84 | result = EC.unwrap(self) & SIMULATION_MASK != 0; 85 | } 86 | 87 | function setSimulationInProgress(EC self) internal pure returns (EC result) { 88 | result = EC.wrap(EC.unwrap(self) | SIMULATION_MASK); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/TransientStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {ExecutionContext, EC} from "./ExecutionContext.sol"; 6 | import {Set, SetStorage} from "./Set.sol"; 7 | 8 | /// @title TransientStorage 9 | /// @custom:security-contact security@euler.xyz 10 | /// @author Euler Labs (https://www.eulerlabs.com/) 11 | /// @notice This contract provides transient storage for the Ethereum Vault Connector. 12 | /// @dev All the variables in this contract are considered transient meaning that their state does not change between 13 | /// invocations. 14 | abstract contract TransientStorage { 15 | using ExecutionContext for EC; 16 | using Set for SetStorage; 17 | 18 | enum SetType { 19 | Account, 20 | Vault 21 | } 22 | 23 | EC internal executionContext; 24 | SetStorage internal accountStatusChecks; 25 | SetStorage internal vaultStatusChecks; 26 | 27 | constructor() { 28 | // set the execution context to non-zero value to always keep the storage slot in non-zero state. 29 | // it allows for cheaper SSTOREs when the execution context is in its default state 30 | executionContext = EC.wrap(1 << ExecutionContext.STAMP_OFFSET); 31 | 32 | // there are two types of data that are stored using SetStorage type: 33 | // - the data that is transient in nature (accountStatusChecks and vaultStatusChecks) 34 | // - the data that is permanent (accountControllers and accountCollaterals from the EthereumVaultConnector 35 | // contract) 36 | 37 | // for the permanent data, there's no need to care that much about optimizations. each account has its two sets. 38 | // usually, an address inserted to either of them won't be removed within the same transaction. the only 39 | // optimization applied (directly in the Set contract) is that on the first element insertion, the stamp is set 40 | // to non-zero value to always keep that storage slot in non-zero state. it allows for cheaper SSTORE when an 41 | // element is inserted again after clearing the set. 42 | 43 | // for the transient data, an address insertion should be as cheap as possible. hence on construction, we store 44 | // dummy values for all the storage slots where the elements will be stored later on. it is important 45 | // considering that both accountStatusChecks and vaultStatusChecks are always cleared at the end of the 46 | // transaction. with dummy values set, the transition from zero to non-zero and back to zero will be 47 | // significantly cheaper than it would be otherwise 48 | accountStatusChecks.initialize(); 49 | vaultStatusChecks.initialize(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/interfaces/IERC1271.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol) 3 | 4 | pragma solidity >=0.8.0; 5 | 6 | /// @dev Interface of the ERC1271 standard signature validation method for 7 | /// contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. 8 | interface IERC1271 { 9 | /// @dev Should return whether the signature provided is valid for the provided data 10 | /// @param hash Hash of the data to be signed 11 | /// @param signature Signature byte array associated with _data 12 | /// @return magicValue Must return the bytes4 magic value 0x1626ba7e (which is a selector of this function) when 13 | /// the signature is valid. 14 | function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); 15 | } 16 | -------------------------------------------------------------------------------- /src/interfaces/IVault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /// @title IVault 6 | /// @custom:security-contact security@euler.xyz 7 | /// @author Euler Labs (https://www.eulerlabs.com/) 8 | /// @notice This interface defines the methods for the Vault for the purpose of integration with the Ethereum Vault 9 | /// Connector. 10 | interface IVault { 11 | /// @notice Disables a controller (this vault) for the authenticated account. 12 | /// @dev A controller is a vault that has been chosen for an account to have special control over account’s 13 | /// balances in the enabled collaterals vaults. User calls this function in order for the vault to disable itself 14 | /// for the account if the conditions are met (i.e. user has repaid debt in full). If the conditions are not met, 15 | /// the function reverts. 16 | function disableController() external; 17 | 18 | /// @notice Checks the status of an account. 19 | /// @dev This function must only deliberately revert if the account status is invalid. If this function reverts due 20 | /// to any other reason, it may render the account unusable with possibly no way to recover funds. 21 | /// @param account The address of the account to be checked. 22 | /// @param collaterals The array of enabled collateral addresses to be considered for the account status check. 23 | /// @return magicValue Must return the bytes4 magic value 0xb168c58f (which is a selector of this function) when 24 | /// account status is valid, or revert otherwise. 25 | function checkAccountStatus( 26 | address account, 27 | address[] calldata collaterals 28 | ) external view returns (bytes4 magicValue); 29 | 30 | /// @notice Checks the status of the vault. 31 | /// @dev This function must only deliberately revert if the vault status is invalid. If this function reverts due to 32 | /// any other reason, it may render some accounts unusable with possibly no way to recover funds. 33 | /// @return magicValue Must return the bytes4 magic value 0x4b3d1223 (which is a selector of this function) when 34 | /// account status is valid, or revert otherwise. 35 | function checkVaultStatus() external returns (bytes4 magicValue); 36 | } 37 | -------------------------------------------------------------------------------- /test/evc/EthereumVaultConnectorHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "./EthereumVaultConnectorScribble.sol"; 6 | import "../utils/mocks/Vault.sol"; 7 | 8 | // helper contract that allows to set EVC's internal state and overrides original 9 | // EVC functions in order to verify the account and vault checks 10 | contract EthereumVaultConnectorHarness is EthereumVaultConnectorScribble { 11 | using ExecutionContext for EC; 12 | using Set for SetStorage; 13 | 14 | address[] internal expectedAccountsChecked; 15 | address[] internal expectedVaultsChecked; 16 | 17 | function isFuzzSender() internal view returns (bool) { 18 | // as per https://fuzzing-docs.diligence.tools/getting-started-1/seed-state 19 | // fuzzer always sends transactions from the EOA while Foundry does it from the test contract 20 | if (msg.sender.code.length == 0) return true; 21 | else return false; 22 | } 23 | 24 | function reset() external { 25 | delete accountStatusChecks; 26 | delete vaultStatusChecks; 27 | delete expectedAccountsChecked; 28 | delete expectedVaultsChecked; 29 | } 30 | 31 | function clearExpectedChecks() public { 32 | delete expectedAccountsChecked; 33 | delete expectedVaultsChecked; 34 | } 35 | 36 | function pushExpectedAccountsCheck(address account) external { 37 | expectedAccountsChecked.push(account); 38 | } 39 | 40 | function pushExpectedVaultsCheck(address vault) external { 41 | expectedVaultsChecked.push(vault); 42 | } 43 | 44 | function getExpectedAccountStatusChecks() external view returns (address[] memory) { 45 | return expectedAccountsChecked; 46 | } 47 | 48 | function getExpectedVaultStatusChecks() external view returns (address[] memory) { 49 | return expectedVaultsChecked; 50 | } 51 | 52 | function setLockdown(bytes19 addressPrefix, bool enabled) external { 53 | if (isFuzzSender()) return; 54 | 55 | ownerLookup[addressPrefix].isLockdownMode = enabled; 56 | } 57 | 58 | function setPermitDisabled(bytes19 addressPrefix, bool enabled) external { 59 | if (isFuzzSender()) return; 60 | 61 | ownerLookup[addressPrefix].isPermitDisabledMode = enabled; 62 | } 63 | 64 | function setChecksDeferred(bool deferred) external { 65 | if (isFuzzSender()) return; 66 | 67 | if (deferred) { 68 | executionContext = executionContext.setChecksDeferred(); 69 | } else { 70 | executionContext = 71 | EC.wrap(EC.unwrap(executionContext) & ~uint256(0xFF0000000000000000000000000000000000000000)); 72 | } 73 | } 74 | 75 | function setChecksInProgress(bool inProgress) external { 76 | if (isFuzzSender()) return; 77 | 78 | if (inProgress) { 79 | executionContext = executionContext.setChecksInProgress(); 80 | } else { 81 | executionContext = 82 | EC.wrap(EC.unwrap(executionContext) & ~uint256(0xFF000000000000000000000000000000000000000000)); 83 | } 84 | } 85 | 86 | function setControlCollateralInProgress(bool inProgress) external { 87 | if (isFuzzSender()) return; 88 | 89 | if (inProgress) { 90 | executionContext = executionContext.setControlCollateralInProgress(); 91 | } else { 92 | executionContext = 93 | EC.wrap(EC.unwrap(executionContext) & ~uint256(0xFF00000000000000000000000000000000000000000000)); 94 | } 95 | } 96 | 97 | function setOperatorAuthenticated(bool authenticated) external { 98 | if (isFuzzSender()) return; 99 | 100 | if (authenticated) { 101 | executionContext = executionContext.setOperatorAuthenticated(); 102 | } else { 103 | executionContext = executionContext.clearOperatorAuthenticated(); 104 | } 105 | } 106 | 107 | function setSimulation(bool inProgress) external { 108 | if (isFuzzSender()) return; 109 | 110 | if (inProgress) { 111 | executionContext = executionContext.setSimulationInProgress(); 112 | } else { 113 | executionContext = 114 | EC.wrap(EC.unwrap(executionContext) & ~uint256(0xFF000000000000000000000000000000000000000000000000)); 115 | } 116 | } 117 | 118 | function setOnBehalfOfAccount(address account) external { 119 | if (isFuzzSender()) return; 120 | executionContext = executionContext.setOnBehalfOfAccount(account); 121 | } 122 | 123 | // function overrides in order to verify the account and vault checks 124 | function requireAccountStatusCheck(address account) public payable override { 125 | super.requireAccountStatusCheck(account); 126 | expectedAccountsChecked.push(account); 127 | } 128 | 129 | function requireVaultStatusCheck() public payable override { 130 | super.requireVaultStatusCheck(); 131 | 132 | expectedVaultsChecked.push(msg.sender); 133 | } 134 | 135 | function requireAccountAndVaultStatusCheck(address account) public payable override { 136 | super.requireAccountAndVaultStatusCheck(account); 137 | 138 | expectedAccountsChecked.push(account); 139 | expectedVaultsChecked.push(msg.sender); 140 | } 141 | 142 | function requireAccountStatusCheckInternal(address account) internal override { 143 | super.requireAccountStatusCheckInternal(account); 144 | 145 | address[] memory controllers = accountControllers[account].get(); 146 | if (controllers.length == 1) { 147 | Vault(controllers[0]).pushAccountStatusChecked(account); 148 | } 149 | } 150 | 151 | function requireVaultStatusCheckInternal(address vault) internal override { 152 | super.requireVaultStatusCheckInternal(vault); 153 | 154 | Vault(vault).pushVaultStatusChecked(); 155 | } 156 | 157 | function verifyVaultStatusChecks() public view { 158 | for (uint256 i = 0; i < expectedVaultsChecked.length; ++i) { 159 | require(Vault(expectedVaultsChecked[i]).getVaultStatusChecked().length == 1, "verifyVaultStatusChecks"); 160 | } 161 | } 162 | 163 | function verifyAccountStatusChecks() public view { 164 | for (uint256 i = 0; i < expectedAccountsChecked.length; ++i) { 165 | address[] memory controllers = accountControllers[expectedAccountsChecked[i]].get(); 166 | uint256 lastAccountStatusCheckTimestamp = accountControllers[expectedAccountsChecked[i]].getMetadata(); 167 | 168 | require(controllers.length <= 1, "verifyAccountStatusChecks/length"); 169 | 170 | if (controllers.length == 0) continue; 171 | 172 | address[] memory accounts = Vault(controllers[0]).getAccountStatusChecked(); 173 | 174 | uint256 counter = 0; 175 | for (uint256 j = 0; j < accounts.length; ++j) { 176 | if (accounts[j] == expectedAccountsChecked[i]) counter++; 177 | } 178 | 179 | require(counter == 1, "verifyAccountStatusChecks/counter"); 180 | require(lastAccountStatusCheckTimestamp == block.timestamp, "verifyAccountStatusChecks/timestamp"); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /test/gas/EVCGas.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | import "../../src/EthereumVaultConnector.sol"; 7 | 8 | contract EVCHarness is EthereumVaultConnector { 9 | function permitHash( 10 | address signer, 11 | address sender, 12 | uint256 nonceNamespace, 13 | uint256 nonce, 14 | uint256 deadline, 15 | uint256 value, 16 | bytes calldata data 17 | ) external view returns (bytes32) { 18 | return getPermitHash(signer, sender, nonceNamespace, nonce, deadline, value, data); 19 | } 20 | 21 | function getIsValidERC1271Signature( 22 | address signer, 23 | bytes32 hash, 24 | bytes memory signature 25 | ) external returns (bool isValid) { 26 | // for compatibility with scribble, do not make this view 27 | return isValidERC1271Signature(signer, hash, signature); 28 | } 29 | } 30 | 31 | contract EVCGas is Test { 32 | using Set for SetStorage; 33 | 34 | EVCHarness evc; 35 | 36 | function setUp() public { 37 | evc = new EVCHarness(); 38 | } 39 | 40 | function testGas_getPermitHash( 41 | address signer, 42 | uint256 nonceNamespace, 43 | uint256 nonce, 44 | uint256 deadline, 45 | uint256 value, 46 | bytes calldata data 47 | ) public view { 48 | evc.permitHash(signer, address(this), nonceNamespace, nonce, deadline, value, data); 49 | } 50 | 51 | function testGas_haveCommonOwner(address a, address b) public view { 52 | evc.haveCommonOwner(a, b); 53 | } 54 | 55 | function testGas_isValidSignature_eoa(address signer, bytes32 hash, bytes memory signature) public { 56 | vm.assume(!evc.haveCommonOwner(signer, address(0))); 57 | vm.assume(signature.length < 100); 58 | evc.getIsValidERC1271Signature(signer, hash, signature); 59 | } 60 | 61 | function testGas_isValidSignature_contract(address signer, bytes32 hash, bytes memory signature) public { 62 | vm.assume(signer != address(evc) && uint160(signer) > 1000); 63 | vm.assume(signature.length < 100); 64 | vm.etch(signer, "ff"); 65 | evc.getIsValidERC1271Signature(signer, hash, signature); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/gas/SetGas.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | import "../../src/Set.sol"; 7 | 8 | contract SetGasTest is Test { 9 | using Set for SetStorage; 10 | 11 | address constant ELEMENT_1 = address(1); 12 | address constant ELEMENT_2 = address(2); 13 | address constant ELEMENT_19 = address(19); 14 | address constant ELEMENT_10 = address(10); 15 | address constant ELEMENT_11 = address(11); 16 | address constant ELEMENT_NOT_FOUND = address(99); 17 | 18 | SetStorage size00; 19 | SetStorage size01; 20 | SetStorage size02; 21 | SetStorage size05; 22 | SetStorage size10; 23 | 24 | function setUp() public { 25 | size01.insert(ELEMENT_1); 26 | 27 | size02.insert(ELEMENT_1); 28 | size02.insert(ELEMENT_2); 29 | 30 | for (uint160 i = 1; i <= 10; ++i) { 31 | size10.insert(address(i)); 32 | } 33 | 34 | for (uint160 i = 1; i <= 5; ++i) { 35 | size05.insert(address(i)); 36 | } 37 | } 38 | 39 | /** 40 | * 41 | */ 42 | /** 43 | * insert * 44 | */ 45 | /** 46 | * 47 | */ 48 | function testGas_insert_size00() public { 49 | size00.insert(ELEMENT_1); 50 | } 51 | 52 | function testGas_insert_size01() public { 53 | size01.insert(ELEMENT_2); 54 | } 55 | 56 | function testGas_insert_size01_duplicateOfFirst() public { 57 | size01.insert(ELEMENT_1); 58 | } 59 | 60 | function testGas_insert_size02() public { 61 | size02.insert(ELEMENT_2); 62 | } 63 | 64 | function testGas_insert_size02_duplicateOfFirst() public { 65 | size02.insert(ELEMENT_1); 66 | } 67 | 68 | function testGas_insert_size02_duplicateOfSecond() public { 69 | size02.insert(ELEMENT_2); 70 | } 71 | 72 | function testGas_insert_size05() public { 73 | size05.insert(ELEMENT_10); 74 | } 75 | 76 | function testGas_insert_size05_duplicateOfFirst() public { 77 | size05.insert(ELEMENT_1); 78 | } 79 | 80 | function testGas_insert_size05_duplicateOfSecond() public { 81 | size05.insert(ELEMENT_2); 82 | } 83 | 84 | function testGas_insert_size05_duplicateOfLast() public { 85 | size05.insert(ELEMENT_19); 86 | } 87 | 88 | function testGas_insert_siz10_reverts() public { 89 | vm.expectRevert(); 90 | size10.insert(ELEMENT_11); 91 | } 92 | 93 | function testGas_insert_size10_duplicateOfFirst() public { 94 | size10.insert(ELEMENT_1); 95 | } 96 | 97 | function testGas_insert_size10_duplicateOfSecond() public { 98 | size10.insert(ELEMENT_2); 99 | } 100 | 101 | function testGas_insert_size10_duplicateOfLast() public { 102 | size10.insert(ELEMENT_10); 103 | } 104 | 105 | /** 106 | * 107 | */ 108 | /** 109 | * contains * 110 | */ 111 | /** 112 | * 113 | */ 114 | function testGas_contains_size00_notFound() public view { 115 | size00.contains(ELEMENT_1); 116 | } 117 | 118 | function testGas_contains_size01_notFound() public view { 119 | size01.contains(ELEMENT_NOT_FOUND); 120 | } 121 | 122 | function testGas_contains_size01_foundAtFirst() public view { 123 | size01.contains(ELEMENT_1); 124 | } 125 | 126 | function testGas_contains_size02_notFound() public view { 127 | size02.contains(ELEMENT_NOT_FOUND); 128 | } 129 | 130 | function testGas_contains_size02_foundAtFirst() public view { 131 | size02.contains(ELEMENT_1); 132 | } 133 | 134 | function testGas_contains_size02_foundAtIndex1() public view { 135 | size02.contains(ELEMENT_2); 136 | } 137 | 138 | function testGas_contains_size05_notFound() public view { 139 | size05.contains(ELEMENT_NOT_FOUND); 140 | } 141 | 142 | function testGas_contains_size05_foundAtFirst() public view { 143 | size05.contains(ELEMENT_1); 144 | } 145 | 146 | function testGas_contains_size05_foundAtIndex1() public view { 147 | size05.contains(ELEMENT_2); 148 | } 149 | 150 | function testGas_contains_size05_foundAtLastIndex() public view { 151 | size05.contains(ELEMENT_19); 152 | } 153 | 154 | function testGas_contains_size10_notFound() public view { 155 | size10.contains(ELEMENT_NOT_FOUND); 156 | } 157 | 158 | function testGas_contains_size10_foundAtFirst() public view { 159 | size10.contains(ELEMENT_1); 160 | } 161 | 162 | function testGas_contains_size10_foundAtIndex1() public view { 163 | size10.contains(ELEMENT_2); 164 | } 165 | 166 | function testGas_contains_size10_foundAtLastIndex() public view { 167 | size10.contains(ELEMENT_10); 168 | } 169 | 170 | /** 171 | * 172 | */ 173 | /** 174 | * remove * 175 | */ 176 | /** 177 | * 178 | */ 179 | function testGas_remove_size00_notFound() public { 180 | size00.remove(ELEMENT_1); 181 | } 182 | 183 | function testGas_remove_size01_notFound() public { 184 | size01.remove(ELEMENT_NOT_FOUND); 185 | } 186 | 187 | function testGas_remove_size01_foundAtFirst() public { 188 | size01.remove(ELEMENT_1); 189 | } 190 | 191 | function testGas_remove_size02_notFound() public { 192 | size02.remove(ELEMENT_NOT_FOUND); 193 | } 194 | 195 | function testGas_remove_size02_foundAtFirst() public { 196 | size02.remove(ELEMENT_1); 197 | } 198 | 199 | function testGas_remove_size02_foundAtIndex1() public { 200 | size02.remove(ELEMENT_2); 201 | } 202 | 203 | function testGas_remove_size05_notFound() public { 204 | size05.remove(ELEMENT_NOT_FOUND); 205 | } 206 | 207 | function testGas_remove_size05_foundAtFirst() public { 208 | size05.remove(ELEMENT_1); 209 | } 210 | 211 | function testGas_remove_size05_foundAtIndex1() public { 212 | size05.remove(ELEMENT_2); 213 | } 214 | 215 | function testGas_remove_size05_foundAtLastIndex() public { 216 | size05.remove(ELEMENT_19); 217 | } 218 | 219 | function testGas_remove_size10_notFound() public { 220 | size10.remove(ELEMENT_NOT_FOUND); 221 | } 222 | 223 | function testGas_remove_size10_foundAtFirst() public { 224 | size10.remove(ELEMENT_1); 225 | } 226 | 227 | function testGas_remove_size10_foundAtIndex1() public { 228 | size10.remove(ELEMENT_2); 229 | } 230 | 231 | function testGas_remove_size10_foundAtLastIndex() public { 232 | size10.remove(ELEMENT_10); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /test/unit/EthereumVaultConnector/AccountAndVaultStatus.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | import "../../../src/Set.sol"; 7 | import "../../evc/EthereumVaultConnectorHarness.sol"; 8 | 9 | contract AccountAndVaultStatusTest is Test { 10 | EthereumVaultConnectorHarness internal evc; 11 | 12 | event AccountStatusCheck(address indexed account, address indexed controller); 13 | event VaultStatusCheck(address indexed vault); 14 | 15 | function setUp() public { 16 | evc = new EthereumVaultConnectorHarness(); 17 | } 18 | 19 | function test_RequireAccountAndVaultStatusCheck( 20 | uint8 numberOfAddresses, 21 | bytes memory seed, 22 | uint48 timestamp, 23 | bool allStatusesValid 24 | ) external { 25 | vm.assume(numberOfAddresses > 0 && numberOfAddresses <= SET_MAX_ELEMENTS); 26 | vm.assume(timestamp > 0); 27 | 28 | address[] memory accounts = new address[](numberOfAddresses); 29 | address[] memory controllers = new address[](numberOfAddresses); 30 | address[] memory vaults = new address[](numberOfAddresses); 31 | for (uint256 i = 0; i < numberOfAddresses; i++) { 32 | accounts[i] = address(uint160(uint256(keccak256(abi.encode(i, seed))))); 33 | controllers[i] = address(new Vault(evc)); 34 | vaults[i] = address(new Vault(evc)); 35 | } 36 | 37 | for (uint256 i = 0; i < numberOfAddresses; i++) { 38 | address account = accounts[i]; 39 | address controller = controllers[i]; 40 | address vault = vaults[i]; 41 | 42 | vm.warp(0); 43 | 44 | vm.prank(account); 45 | evc.enableController(account, controller); 46 | Vault(controller).clearChecks(); 47 | evc.clearExpectedChecks(); 48 | 49 | vm.warp(timestamp); 50 | 51 | // check all the options: states are ok, states are violated with 52 | // vault/controller returning false and reverting 53 | Vault(controller).setAccountStatusState( 54 | allStatusesValid ? 0 : uint160(account) % 3 == 0 ? 0 : uint160(account) % 3 == 1 ? 1 : 2 55 | ); 56 | 57 | Vault(vault).setVaultStatusState( 58 | allStatusesValid ? 0 : uint160(vault) % 3 == 0 ? 0 : uint160(vault) % 3 == 1 ? 1 : 2 59 | ); 60 | 61 | bool alreadyExpectsRevert; 62 | if (allStatusesValid || uint160(account) % 3 == 0) { 63 | vm.expectEmit(true, true, false, false, address(evc)); 64 | emit AccountStatusCheck(account, controller); 65 | } else { 66 | alreadyExpectsRevert = true; 67 | 68 | vm.expectRevert( 69 | uint160(account) % 3 == 1 ? bytes("account status violation") : abi.encode(bytes4(uint32(2))) 70 | ); 71 | } 72 | 73 | if (allStatusesValid || uint160(vault) % 3 == 0 && !alreadyExpectsRevert) { 74 | vm.expectEmit(true, false, false, false, address(evc)); 75 | emit VaultStatusCheck(vault); 76 | } else if (!alreadyExpectsRevert) { 77 | alreadyExpectsRevert = true; 78 | 79 | vm.expectRevert( 80 | uint160(vault) % 3 == 1 ? bytes("vault status violation") : abi.encode(bytes4(uint32(1))) 81 | ); 82 | } 83 | 84 | vm.prank(vault); 85 | evc.requireAccountAndVaultStatusCheck(account); 86 | 87 | evc.verifyAccountStatusChecks(); 88 | evc.verifyVaultStatusChecks(); 89 | 90 | if (allStatusesValid || uint160(account) % 3 == 0 && !alreadyExpectsRevert) { 91 | assertTrue(evc.getLastAccountStatusCheckTimestamp(account) == block.timestamp); 92 | } else { 93 | assertFalse(evc.getLastAccountStatusCheckTimestamp(account) == block.timestamp); 94 | } 95 | 96 | Vault(controller).clearChecks(); 97 | Vault(vault).clearChecks(); 98 | evc.clearExpectedChecks(); 99 | } 100 | } 101 | 102 | function test_WhenDeferred_RequireAccountAndVaultStatusCheck( 103 | uint8 numberOfAddresses, 104 | bytes memory seed, 105 | uint48 timestamp 106 | ) external { 107 | vm.assume(numberOfAddresses > 0 && numberOfAddresses <= SET_MAX_ELEMENTS); 108 | vm.assume(timestamp > 0); 109 | 110 | address[] memory accounts = new address[](numberOfAddresses); 111 | address[] memory controllers = new address[](numberOfAddresses); 112 | address[] memory vaults = new address[](numberOfAddresses); 113 | for (uint256 i = 0; i < numberOfAddresses; i++) { 114 | accounts[i] = address(uint160(uint256(keccak256(abi.encode(i, seed))))); 115 | controllers[i] = address(new Vault(evc)); 116 | vaults[i] = address(new Vault(evc)); 117 | } 118 | 119 | for (uint256 i = 0; i < numberOfAddresses; i++) { 120 | evc.setChecksDeferred(false); 121 | 122 | address account = accounts[i]; 123 | address controller = controllers[i]; 124 | address vault = vaults[i]; 125 | 126 | vm.warp(0); 127 | 128 | vm.prank(account); 129 | evc.enableController(account, controller); 130 | Vault(controller).setAccountStatusState(1); 131 | Vault(vault).setVaultStatusState(1); 132 | 133 | vm.warp(timestamp); 134 | 135 | // status checks will be scheduled for later due to deferred state 136 | evc.setChecksDeferred(true); 137 | 138 | // even though the account status state and the vault status state were 139 | // set to 1 which should revert, it doesn't because in checks deferral 140 | // we only add the accounts to the set so that the checks can be performed 141 | // later 142 | assertFalse(evc.isAccountStatusCheckDeferred(account)); 143 | assertFalse(evc.isVaultStatusCheckDeferred(vault)); 144 | 145 | vm.prank(vault); 146 | evc.requireAccountAndVaultStatusCheck(account); 147 | 148 | assertTrue(evc.isAccountStatusCheckDeferred(account)); 149 | assertTrue(evc.isVaultStatusCheckDeferred(vault)); 150 | assertTrue(evc.getLastAccountStatusCheckTimestamp(account) == 0); 151 | 152 | evc.reset(); 153 | } 154 | } 155 | 156 | function test_RevertIfChecksReentrancy_RequireAccountAndVaultStatusCheck( 157 | uint8 index, 158 | address[] calldata accounts 159 | ) external { 160 | vm.assume(index < accounts.length); 161 | vm.assume(accounts.length > 0 && accounts.length <= SET_MAX_ELEMENTS); 162 | 163 | address vault = address(new Vault(evc)); 164 | 165 | evc.setChecksInProgress(true); 166 | 167 | vm.expectRevert(abi.encodeWithSelector(Errors.EVC_ChecksReentrancy.selector)); 168 | vm.prank(vault); 169 | evc.requireAccountAndVaultStatusCheck(accounts[index]); 170 | 171 | vm.expectRevert(abi.encodeWithSelector(Errors.EVC_ChecksReentrancy.selector)); 172 | evc.getLastAccountStatusCheckTimestamp(accounts[index]); 173 | 174 | evc.setChecksInProgress(false); 175 | vm.prank(vault); 176 | evc.requireAccountAndVaultStatusCheck(accounts[index]); 177 | evc.getLastAccountStatusCheckTimestamp(accounts[index]); 178 | } 179 | 180 | function test_AcquireChecksLock_RequireAccountAndVaultStatusChecks( 181 | uint8 numberOfAddresses, 182 | bytes memory seed 183 | ) external { 184 | vm.assume(numberOfAddresses > 0 && numberOfAddresses <= SET_MAX_ELEMENTS); 185 | 186 | address[] memory accounts = new address[](numberOfAddresses); 187 | address[] memory controllers = new address[](numberOfAddresses); 188 | address[] memory vaults = new address[](numberOfAddresses); 189 | for (uint256 i = 0; i < numberOfAddresses; i++) { 190 | accounts[i] = address(uint160(uint256(keccak256(abi.encode(i, seed))))); 191 | 192 | controllers[i] = address(new VaultMalicious(evc)); 193 | vaults[i] = address(new VaultMalicious(evc)); 194 | 195 | vm.prank(accounts[i]); 196 | evc.enableController(accounts[i], controllers[i]); 197 | 198 | VaultMalicious(controllers[i]).setExpectedErrorSelector(Errors.EVC_ChecksReentrancy.selector); 199 | 200 | // function will revert with EVC_AccountStatusViolation according to VaultMalicious implementation 201 | vm.expectRevert(bytes("malicious vault")); 202 | vm.prank(vaults[i]); 203 | evc.requireAccountAndVaultStatusCheck(accounts[i]); 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /test/unit/EthereumVaultConnector/ControlCollateral.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | import "../../evc/EthereumVaultConnectorHarness.sol"; 7 | 8 | contract EthereumVaultConnectorHandler is EthereumVaultConnectorHarness { 9 | using Set for SetStorage; 10 | 11 | function handlerControlCollateral( 12 | address targetContract, 13 | address onBehalfOfAccount, 14 | uint256 value, 15 | bytes calldata data 16 | ) public payable returns (bytes memory result) { 17 | (bool success,) = msg.sender.call(abi.encodeWithSelector(Vault.clearChecks.selector)); 18 | success; 19 | clearExpectedChecks(); 20 | 21 | result = super.controlCollateral(targetContract, onBehalfOfAccount, value, data); 22 | 23 | verifyVaultStatusChecks(); 24 | verifyAccountStatusChecks(); 25 | } 26 | } 27 | 28 | contract ControlCollateralTest is Test { 29 | EthereumVaultConnectorHandler internal evc; 30 | 31 | event CallWithContext( 32 | address indexed caller, 33 | bytes19 indexed onBehalfOfAddressPrefix, 34 | address onBehalfOfAccount, 35 | address indexed targetContract, 36 | bytes4 selector 37 | ); 38 | 39 | function setUp() public { 40 | evc = new EthereumVaultConnectorHandler(); 41 | } 42 | 43 | function test_ControlCollateral(address alice, uint96 seed) public { 44 | vm.assume(alice != address(0) && alice != address(evc)); 45 | 46 | address collateral = address(new Vault(evc)); 47 | address controller = address(new Vault(evc)); 48 | vm.assume(collateral != address(evc)); 49 | vm.assume(!evc.haveCommonOwner(alice, controller)); 50 | 51 | vm.prank(alice); 52 | evc.enableCollateral(alice, collateral); 53 | 54 | vm.prank(alice); 55 | evc.enableController(alice, controller); 56 | 57 | bytes memory data = abi.encodeWithSelector( 58 | Target(collateral).controlCollateralTest.selector, address(evc), address(evc), seed, alice 59 | ); 60 | 61 | vm.deal(controller, seed); 62 | vm.expectEmit(true, true, true, true, address(evc)); 63 | emit CallWithContext( 64 | controller, evc.getAddressPrefix(alice), alice, collateral, Target.controlCollateralTest.selector 65 | ); 66 | vm.prank(controller); 67 | bytes memory result = evc.handlerControlCollateral{value: seed}(collateral, alice, seed, data); 68 | assertEq(abi.decode(result, (uint256)), seed); 69 | 70 | evc.clearExpectedChecks(); 71 | Vault(controller).clearChecks(); 72 | } 73 | 74 | function test_RevertIfChecksReentrancy_ControlCollateral(address alice, uint256 seed) public { 75 | vm.assume(alice != address(0) && alice != address(evc)); 76 | 77 | address collateral = address(new Vault(evc)); 78 | address controller = address(new Vault(evc)); 79 | vm.assume(collateral != address(evc)); 80 | 81 | vm.prank(alice); 82 | evc.enableCollateral(alice, collateral); 83 | 84 | vm.prank(alice); 85 | evc.enableController(alice, controller); 86 | 87 | evc.setChecksInProgress(true); 88 | 89 | bytes memory data = abi.encodeWithSelector( 90 | Target(address(evc)).controlCollateralTest.selector, address(evc), address(evc), seed, alice 91 | ); 92 | 93 | vm.deal(alice, seed); 94 | vm.prank(alice); 95 | vm.expectRevert(Errors.EVC_ChecksReentrancy.selector); 96 | evc.controlCollateral{value: seed}(collateral, alice, seed, data); 97 | } 98 | 99 | function test_RevertIfControlCollateralReentrancy_ControlCollateral(address alice, uint256 seed) public { 100 | vm.assume(alice != address(0) && alice != address(evc)); 101 | 102 | address collateral = address(new Vault(evc)); 103 | address controller = address(new Vault(evc)); 104 | vm.assume(collateral != address(evc)); 105 | 106 | vm.prank(alice); 107 | evc.enableCollateral(alice, collateral); 108 | 109 | vm.prank(alice); 110 | evc.enableController(alice, controller); 111 | 112 | evc.setControlCollateralInProgress(true); 113 | 114 | bytes memory data = abi.encodeWithSelector( 115 | Target(address(evc)).controlCollateralTest.selector, address(evc), address(evc), seed, alice 116 | ); 117 | 118 | vm.deal(alice, seed); 119 | vm.prank(alice); 120 | vm.expectRevert(Errors.EVC_ControlCollateralReentrancy.selector); 121 | evc.controlCollateral{value: seed}(collateral, alice, seed, data); 122 | } 123 | 124 | function test_RevertIfNoControllerEnabled_ControlCollateral(address alice, uint256 seed) public { 125 | vm.assume(alice != address(0) && alice != address(evc)); 126 | 127 | address collateral = address(new Vault(evc)); 128 | address controller = address(new Vault(evc)); 129 | 130 | vm.assume(collateral != address(evc)); 131 | 132 | vm.prank(alice); 133 | evc.enableCollateral(alice, collateral); 134 | 135 | bytes memory data = abi.encodeWithSelector( 136 | Target(collateral).controlCollateralTest.selector, address(evc), address(evc), seed, alice 137 | ); 138 | 139 | vm.deal(controller, seed); 140 | vm.prank(controller); 141 | vm.expectRevert(Errors.EVC_ControllerViolation.selector); 142 | evc.controlCollateral{value: seed}(collateral, alice, seed, data); 143 | } 144 | 145 | function test_RevertIfMultipleControllersEnabled_ControlCollateral(address alice, uint256 seed) public { 146 | vm.assume(alice != address(0) && alice != address(evc)); 147 | 148 | address collateral = address(new Vault(evc)); 149 | address controller_1 = address(new Vault(evc)); 150 | address controller_2 = address(new Vault(evc)); 151 | 152 | vm.assume(collateral != address(evc)); 153 | 154 | // mock checks deferred to enable multiple controllers 155 | evc.setChecksDeferred(true); 156 | 157 | vm.prank(alice); 158 | evc.enableCollateral(alice, collateral); 159 | 160 | vm.prank(alice); 161 | evc.enableController(alice, controller_1); 162 | 163 | vm.prank(alice); 164 | evc.enableController(alice, controller_2); 165 | 166 | bytes memory data = abi.encodeWithSelector( 167 | Target(collateral).controlCollateralTest.selector, address(evc), address(evc), seed, alice 168 | ); 169 | 170 | vm.deal(controller_1, seed); 171 | vm.prank(controller_1); 172 | vm.expectRevert(Errors.EVC_ControllerViolation.selector); 173 | evc.controlCollateral{value: seed}(collateral, alice, seed, data); 174 | } 175 | 176 | function test_RevertIfMsgSenderIsNotEnabledController_ControlCollateral( 177 | address alice, 178 | address randomAddress, 179 | uint256 seed 180 | ) public { 181 | vm.assume(alice != address(0) && alice != address(evc)); 182 | vm.assume(uint160(randomAddress) > 10 && randomAddress != address(evc)); 183 | 184 | address collateral = address(new Vault(evc)); 185 | address controller = address(new Vault(evc)); 186 | 187 | vm.assume(collateral != address(evc)); 188 | vm.assume(randomAddress != controller); 189 | 190 | vm.prank(alice); 191 | evc.enableCollateral(alice, collateral); 192 | 193 | vm.prank(alice); 194 | evc.enableController(alice, controller); 195 | 196 | bytes memory data = abi.encodeWithSelector( 197 | Target(collateral).controlCollateralTest.selector, address(evc), address(evc), seed, alice 198 | ); 199 | 200 | vm.deal(randomAddress, seed); 201 | vm.prank(randomAddress); 202 | vm.expectRevert(abi.encodeWithSelector(Errors.EVC_NotAuthorized.selector)); 203 | evc.controlCollateral{value: seed}(collateral, alice, seed, data); 204 | } 205 | 206 | function test_RevertIfTargetContractIsNotEnabledCollateral_ControlCollateral( 207 | address alice, 208 | address targetContract, 209 | uint256 seed 210 | ) public { 211 | vm.assume(alice != address(0) && alice != address(evc)); 212 | vm.assume(targetContract != address(evc)); 213 | 214 | address collateral = address(new Vault(evc)); 215 | address controller = address(new Vault(evc)); 216 | 217 | vm.assume(targetContract != collateral && targetContract != controller); 218 | 219 | vm.prank(alice); 220 | evc.enableCollateral(alice, collateral); 221 | 222 | vm.prank(alice); 223 | evc.enableController(alice, controller); 224 | 225 | bytes memory data = abi.encodeWithSelector( 226 | Target(collateral).controlCollateralTest.selector, address(evc), address(evc), seed, alice 227 | ); 228 | 229 | vm.deal(controller, seed); 230 | vm.prank(controller); 231 | vm.expectRevert(abi.encodeWithSelector(Errors.EVC_NotAuthorized.selector)); 232 | evc.controlCollateral{value: seed}(targetContract, alice, seed, data); 233 | } 234 | 235 | function test_RevertIfValueExceedsBalance_ControlCollateral(address alice, uint128 seed) public { 236 | vm.assume(alice != address(0) && alice != address(evc)); 237 | vm.assume(seed > 0); 238 | 239 | address collateral = address(new Vault(evc)); 240 | address controller = address(new Vault(evc)); 241 | vm.assume(collateral != address(evc) && controller != address(evc)); 242 | 243 | vm.prank(alice); 244 | evc.enableCollateral(alice, collateral); 245 | 246 | vm.prank(alice); 247 | evc.enableController(alice, controller); 248 | 249 | bytes memory data = abi.encodeWithSelector( 250 | Target(address(evc)).controlCollateralTest.selector, address(evc), address(evc), seed, alice 251 | ); 252 | 253 | // reverts if value exceeds balance 254 | vm.deal(controller, seed); 255 | vm.prank(controller); 256 | vm.expectRevert(Errors.EVC_InvalidValue.selector); 257 | evc.controlCollateral{value: seed - 1}(collateral, alice, seed, data); 258 | 259 | // succeeds if value does not exceed balance 260 | vm.prank(controller); 261 | evc.controlCollateral{value: seed}(collateral, alice, seed, data); 262 | } 263 | 264 | function test_RevertIfInternalCallIsUnsuccessful_ControlCollateral(address alice) public { 265 | // call setUp() explicitly for Diligence Fuzzing tool to pass 266 | setUp(); 267 | 268 | vm.assume(alice != address(0)); 269 | vm.assume(alice != address(evc)); 270 | 271 | address collateral = address(new Vault(evc)); 272 | address controller = address(new Vault(evc)); 273 | vm.assume(collateral != address(evc) && controller != address(evc)); 274 | 275 | vm.prank(alice); 276 | evc.enableCollateral(alice, collateral); 277 | 278 | vm.prank(alice); 279 | evc.enableController(alice, controller); 280 | 281 | bytes memory data = abi.encodeWithSelector(Target(collateral).revertEmptyTest.selector); 282 | 283 | vm.prank(controller); 284 | vm.expectRevert(Errors.EVC_EmptyError.selector); 285 | evc.controlCollateral(collateral, alice, 0, data); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /test/unit/EthereumVaultConnector/ControllersManagement.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | import "../../evc/EthereumVaultConnectorHarness.sol"; 7 | 8 | contract EthereumVaultConnectorHandler is EthereumVaultConnectorHarness { 9 | using ExecutionContext for EC; 10 | using Set for SetStorage; 11 | 12 | function handlerEnableController(address account, address vault) external { 13 | clearExpectedChecks(); 14 | Vault(vault).clearChecks(); 15 | 16 | super.enableController(account, vault); 17 | 18 | if (executionContext.areChecksDeferred()) return; 19 | 20 | expectedAccountsChecked.push(account); 21 | 22 | verifyAccountStatusChecks(); 23 | } 24 | 25 | function handlerDisableController(address account) external { 26 | clearExpectedChecks(); 27 | Vault(msg.sender).clearChecks(); 28 | 29 | super.disableController(account); 30 | 31 | if (executionContext.areChecksDeferred()) return; 32 | 33 | expectedAccountsChecked.push(account); 34 | 35 | verifyAccountStatusChecks(); 36 | } 37 | } 38 | 39 | contract ControllersManagementTest is Test { 40 | EthereumVaultConnectorHandler internal evc; 41 | 42 | event ControllerStatus(address indexed account, address indexed controller, bool enabled); 43 | 44 | function setUp() public { 45 | evc = new EthereumVaultConnectorHandler(); 46 | } 47 | 48 | function test_ControllersManagement(address alice, uint8 subAccountId, uint256 seed) public { 49 | vm.assume(alice != address(0) && alice != address(evc)); 50 | vm.assume(seed > 1000); 51 | 52 | address account = address(uint160(uint160(alice) ^ subAccountId)); 53 | vm.assume(account.code.length == 0); 54 | 55 | // test controllers management with use of an operator 56 | address msgSender = alice; 57 | if (seed % 2 == 0 && !evc.haveCommonOwner(account, address(uint160(seed)))) { 58 | msgSender = address(uint160(uint256(keccak256(abi.encode(seed))))); 59 | vm.prank(alice); 60 | evc.setAccountOperator(account, msgSender, true); 61 | } 62 | 63 | // enabling controller 64 | address vault = address(new Vault(evc)); 65 | 66 | assertFalse(evc.isControllerEnabled(account, vault)); 67 | address[] memory controllersPre = evc.getControllers(account); 68 | 69 | vm.expectEmit(true, true, false, true, address(evc)); 70 | emit ControllerStatus(account, vault, true); 71 | vm.prank(msgSender); 72 | evc.handlerEnableController(account, vault); 73 | 74 | address[] memory controllersPost = evc.getControllers(account); 75 | assertEq(controllersPost.length, controllersPre.length + 1); 76 | assertEq(controllersPost[controllersPost.length - 1], vault); 77 | assertTrue(evc.isControllerEnabled(account, vault)); 78 | 79 | // enabling the same controller again should succeed (duplicate will not be added and the event won't be 80 | // emitted) 81 | assertTrue(evc.isControllerEnabled(account, vault)); 82 | controllersPre = evc.getControllers(account); 83 | 84 | vm.prank(msgSender); 85 | evc.handlerEnableController(account, vault); 86 | 87 | controllersPost = evc.getControllers(account); 88 | 89 | assertEq(controllersPost.length, controllersPre.length); 90 | assertEq(controllersPost[0], controllersPre[0]); 91 | assertTrue(evc.isControllerEnabled(account, vault)); 92 | 93 | // trying to enable second controller will throw on the account status check 94 | address otherVault = address(new Vault(evc)); 95 | 96 | vm.prank(msgSender); 97 | vm.expectRevert(Errors.EVC_ControllerViolation.selector); 98 | evc.handlerEnableController(account, otherVault); 99 | 100 | // only the controller vault can disable itself 101 | assertTrue(evc.isControllerEnabled(account, vault)); 102 | controllersPre = evc.getControllers(account); 103 | 104 | vm.prank(msgSender); 105 | vm.expectEmit(true, true, false, true, address(evc)); 106 | emit ControllerStatus(account, vault, false); 107 | Vault(vault).call(address(evc), abi.encodeWithSelector(evc.handlerDisableController.selector, account)); 108 | 109 | controllersPost = evc.getControllers(account); 110 | 111 | assertEq(controllersPost.length, controllersPre.length - 1); 112 | assertEq(controllersPost.length, 0); 113 | assertFalse(evc.isControllerEnabled(account, vault)); 114 | } 115 | 116 | function test_RevertIfNotOwnerOrNotOperator_EnableController(address alice, address bob) public { 117 | vm.assume(alice != address(0) && alice != address(evc) && bob != address(0) && bob != address(evc)); 118 | vm.assume(!evc.haveCommonOwner(alice, bob)); 119 | 120 | address vault = address(new Vault(evc)); 121 | 122 | vm.prank(alice); 123 | vm.expectRevert(Errors.EVC_NotAuthorized.selector); 124 | evc.handlerEnableController(bob, vault); 125 | 126 | vm.prank(bob); 127 | evc.setAccountOperator(bob, alice, true); 128 | 129 | vm.prank(alice); 130 | evc.handlerEnableController(bob, vault); 131 | } 132 | 133 | function test_RevertIfProgressReentrancy_ControllersManagement(address alice) public { 134 | vm.assume(alice != address(evc)); 135 | 136 | address vault = address(new Vault(evc)); 137 | 138 | evc.setChecksInProgress(true); 139 | 140 | vm.prank(alice); 141 | vm.expectRevert(Errors.EVC_ChecksReentrancy.selector); 142 | evc.enableController(alice, vault); 143 | 144 | evc.setChecksInProgress(false); 145 | 146 | vm.prank(alice); 147 | evc.enableController(alice, vault); 148 | 149 | evc.setChecksInProgress(true); 150 | 151 | vm.prank(vault); 152 | vm.expectRevert(Errors.EVC_ChecksReentrancy.selector); 153 | evc.disableController(alice); 154 | 155 | evc.setChecksInProgress(false); 156 | 157 | vm.prank(vault); 158 | evc.disableController(alice); 159 | } 160 | 161 | function test_RevertIfControlCollateralReentrancy_ControllersManagement(address alice) public { 162 | vm.assume(alice != address(evc)); 163 | 164 | address vault = address(new Vault(evc)); 165 | 166 | evc.setControlCollateralInProgress(true); 167 | 168 | vm.prank(alice); 169 | vm.expectRevert(Errors.EVC_ControlCollateralReentrancy.selector); 170 | evc.enableController(alice, vault); 171 | 172 | evc.setControlCollateralInProgress(false); 173 | 174 | vm.prank(alice); 175 | evc.enableController(alice, vault); 176 | 177 | evc.setControlCollateralInProgress(true); 178 | 179 | vm.prank(vault); 180 | vm.expectRevert(Errors.EVC_ControlCollateralReentrancy.selector); 181 | evc.disableController(alice); 182 | 183 | evc.setControlCollateralInProgress(false); 184 | 185 | vm.prank(vault); 186 | evc.disableController(alice); 187 | } 188 | 189 | function test_RevertIfInvalidVault_ControllersManagement(address alice) public { 190 | vm.assume(alice != address(evc)); 191 | vm.prank(alice); 192 | vm.expectRevert(Errors.EVC_InvalidAddress.selector); 193 | evc.enableController(alice, address(evc)); 194 | } 195 | 196 | function test_RevertIfAccountStatusViolated_ControllersManagement(address alice) public { 197 | vm.assume(alice != address(evc)); 198 | 199 | address vault = address(new Vault(evc)); 200 | 201 | Vault(vault).setAccountStatusState(1); // account status is violated 202 | 203 | vm.prank(alice); 204 | vm.expectRevert("account status violation"); 205 | evc.handlerEnableController(alice, vault); 206 | 207 | vm.prank(alice); 208 | // succeeds as there's no controller to perform the account status check 209 | Vault(vault).call(address(evc), abi.encodeWithSelector(evc.handlerDisableController.selector, alice)); 210 | 211 | Vault(vault).setAccountStatusState(1); // account status is still violated 212 | 213 | vm.prank(alice); 214 | // succeeds as there's no controller to perform the account status check 215 | evc.enableCollateral(alice, vault); 216 | 217 | Vault(vault).setAccountStatusState(0); // account status is no longer violated in order to enable controller 218 | 219 | vm.prank(alice); 220 | evc.handlerEnableController(alice, vault); 221 | 222 | Vault(vault).setAccountStatusState(1); // account status is violated again 223 | 224 | vm.prank(alice); 225 | // it won't succeed as this time we have a controller so the account status check is performed 226 | vm.expectRevert("account status violation"); 227 | evc.enableCollateral(alice, vault); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /test/unit/EthereumVaultConnector/GetExecutionContext.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | import "../../evc/EthereumVaultConnectorHarness.sol"; 7 | 8 | contract GetExecutionContextTest is Test { 9 | EthereumVaultConnectorHarness internal evc; 10 | 11 | function setUp() public { 12 | evc = new EthereumVaultConnectorHarness(); 13 | } 14 | 15 | function test_GetExecutionContext(address account, uint8 seed) external { 16 | vm.assume(account != address(0) && account != address(evc)); 17 | 18 | address controller = address(new Vault(evc)); 19 | 20 | vm.expectRevert(Errors.EVC_OnBehalfOfAccountNotAuthenticated.selector); 21 | evc.getCurrentOnBehalfOfAccount(controller); 22 | 23 | uint256 context = evc.getRawExecutionContext(); 24 | assertEq(context, 1 << 200); 25 | 26 | if (seed % 2 == 0) { 27 | vm.prank(account); 28 | evc.enableController(account, controller); 29 | } 30 | evc.setChecksDeferred(seed % 3 == 0 ? true : false); 31 | evc.setOnBehalfOfAccount(account); 32 | evc.setChecksInProgress(seed % 4 == 0 ? true : false); 33 | evc.setControlCollateralInProgress(seed % 5 == 0 ? true : false); 34 | evc.setOperatorAuthenticated(seed % 6 == 0 ? true : false); 35 | evc.setSimulation(seed % 7 == 0 ? true : false); 36 | 37 | (address onBehalfOfAccount, bool controllerEnabled) = evc.getCurrentOnBehalfOfAccount(controller); 38 | context = evc.getRawExecutionContext(); 39 | 40 | assertEq(onBehalfOfAccount, account); 41 | assertEq(controllerEnabled, seed % 2 == 0 ? true : false); 42 | assertEq( 43 | context & 0x000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, uint256(uint160(account)) 44 | ); 45 | assertEq( 46 | context & 0x0000000000000000000000FF0000000000000000000000000000000000000000 != 0, 47 | seed % 3 == 0 ? true : false 48 | ); 49 | assertEq(evc.areChecksDeferred(), seed % 3 == 0 ? true : false); 50 | assertEq( 51 | context & 0x00000000000000000000FF000000000000000000000000000000000000000000 != 0, 52 | seed % 4 == 0 ? true : false 53 | ); 54 | assertEq(evc.areChecksInProgress(), seed % 4 == 0 ? true : false); 55 | assertEq( 56 | context & 0x000000000000000000FF00000000000000000000000000000000000000000000 != 0, 57 | seed % 5 == 0 ? true : false 58 | ); 59 | assertEq(evc.isControlCollateralInProgress(), seed % 5 == 0 ? true : false); 60 | assertEq( 61 | context & 0x0000000000000000FF0000000000000000000000000000000000000000000000 != 0, 62 | seed % 6 == 0 ? true : false 63 | ); 64 | assertEq(evc.isOperatorAuthenticated(), seed % 6 == 0 ? true : false); 65 | assertEq( 66 | context & 0x00000000000000FF000000000000000000000000000000000000000000000000 != 0, 67 | seed % 7 == 0 ? true : false 68 | ); 69 | assertEq(evc.isSimulationInProgress(), seed % 7 == 0 ? true : false); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/unit/EthereumVaultConnector/IsAccountStatusCheckDeferred.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | import "../../../src/Set.sol"; 7 | import "../../evc/EthereumVaultConnectorHarness.sol"; 8 | 9 | contract IsAccountStatusCheckDeferredTest is Test { 10 | EthereumVaultConnectorHarness internal evc; 11 | 12 | function setUp() public { 13 | evc = new EthereumVaultConnectorHarness(); 14 | } 15 | 16 | function test_IsAccountStatusCheckDeferred(uint8 numberOfAccounts, bytes memory seed) external { 17 | vm.assume(numberOfAccounts <= SET_MAX_ELEMENTS); 18 | 19 | for (uint256 i = 0; i < numberOfAccounts; ++i) { 20 | evc.setChecksDeferred(false); 21 | 22 | address account = address(uint160(uint256(keccak256(abi.encode(i, seed))))); 23 | assertFalse(evc.isAccountStatusCheckDeferred(account)); 24 | 25 | evc.requireAccountStatusCheck(account); 26 | assertFalse(evc.isAccountStatusCheckDeferred(account)); 27 | 28 | evc.setChecksDeferred(true); 29 | 30 | evc.requireAccountStatusCheck(account); 31 | assertTrue(evc.isAccountStatusCheckDeferred(account)); 32 | 33 | evc.reset(); 34 | } 35 | } 36 | 37 | function test_RevertIfChecksInProgress_IsAccountStatusCheckDeferred(address account) external { 38 | evc.setChecksInProgress(false); 39 | assertFalse(evc.isAccountStatusCheckDeferred(account)); 40 | 41 | evc.setChecksInProgress(true); 42 | vm.expectRevert(Errors.EVC_ChecksReentrancy.selector); 43 | evc.isAccountStatusCheckDeferred(account); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/unit/EthereumVaultConnector/IsVaultStatusCheckDeferred.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | import "../../../src/Set.sol"; 7 | import "../../evc/EthereumVaultConnectorHarness.sol"; 8 | 9 | contract IsVaultStatusCheckDeferredTest is Test { 10 | EthereumVaultConnectorHarness internal evc; 11 | 12 | function setUp() public { 13 | evc = new EthereumVaultConnectorHarness(); 14 | } 15 | 16 | function test_IsVaultStatusCheckDeferred(uint8 numberOfVaults) external { 17 | vm.assume(numberOfVaults <= SET_MAX_ELEMENTS); 18 | 19 | for (uint256 i = 0; i < numberOfVaults; ++i) { 20 | evc.setChecksDeferred(false); 21 | 22 | address vault = address(new Vault(evc)); 23 | assertFalse(evc.isVaultStatusCheckDeferred(vault)); 24 | 25 | vm.prank(vault); 26 | evc.requireVaultStatusCheck(); 27 | assertFalse(evc.isVaultStatusCheckDeferred(vault)); 28 | 29 | evc.setChecksDeferred(true); 30 | 31 | vm.prank(vault); 32 | evc.requireVaultStatusCheck(); 33 | assertTrue(evc.isVaultStatusCheckDeferred(vault)); 34 | 35 | evc.reset(); 36 | } 37 | } 38 | 39 | function test_RevertIfChecksInProgress_IsVaultStatusCheckDeferred(address vault) external { 40 | evc.setChecksInProgress(false); 41 | assertFalse(evc.isVaultStatusCheckDeferred(vault)); 42 | 43 | evc.setChecksInProgress(true); 44 | vm.expectRevert(Errors.EVC_ChecksReentrancy.selector); 45 | evc.isVaultStatusCheckDeferred(vault); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/unit/EthereumVaultConnector/POC.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "forge-std/Test.sol"; 6 | import {EthereumVaultConnector} from "../../../src/EthereumVaultConnector.sol"; 7 | 8 | contract POC_Test is Test { 9 | EthereumVaultConnector internal evc; 10 | 11 | function setUp() public { 12 | evc = new EthereumVaultConnector(); 13 | } 14 | 15 | function test_POC() external {} 16 | } 17 | -------------------------------------------------------------------------------- /test/unit/EthereumVaultConnector/Receive.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | import "../../evc/EthereumVaultConnectorHarness.sol"; 7 | 8 | contract ReceiveTest is Test { 9 | EthereumVaultConnectorHarness internal evc; 10 | 11 | function setUp() public { 12 | evc = new EthereumVaultConnectorHarness(); 13 | } 14 | 15 | function test_Receive(uint64 value) external { 16 | vm.assume(value > 0); 17 | vm.deal(address(this), 2 * uint256(value)); 18 | 19 | // fails when checks are not deferred 20 | (bool success,) = address(evc).call{value: value}(""); 21 | assertFalse(success); 22 | 23 | evc.setChecksDeferred(true); 24 | (success,) = address(evc).call{value: value}(""); 25 | assertTrue(success); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/unit/EthereumVaultConnector/SetLockdownMode.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | import "../../evc/EthereumVaultConnectorHarness.sol"; 7 | 8 | contract SetLockdownModeTest is Test { 9 | EthereumVaultConnectorHarness internal evc; 10 | 11 | event LockdownModeStatus(bytes19 indexed addressPrefix, bool status); 12 | 13 | function setUp() public { 14 | evc = new EthereumVaultConnectorHarness(); 15 | } 16 | 17 | function test_SetLockdownMode(address alice) public { 18 | vm.assume(alice != address(0) && alice != address(evc)); 19 | 20 | bytes19 addressPrefix = evc.getAddressPrefix(alice); 21 | assertEq(evc.isLockdownMode(addressPrefix), false); 22 | 23 | // no-op when setting lockdown mode to the same value 24 | vm.prank(alice); 25 | evc.setLockdownMode(addressPrefix, false); 26 | assertEq(evc.isLockdownMode(addressPrefix), false); 27 | 28 | vm.expectEmit(true, true, false, false, address(evc)); 29 | emit LockdownModeStatus(addressPrefix, true); 30 | 31 | vm.prank(alice); 32 | evc.setLockdownMode(addressPrefix, true); 33 | assertEq(evc.isLockdownMode(addressPrefix), true); 34 | 35 | // no-op when setting lockdown mode to the same value 36 | vm.prank(alice); 37 | evc.setLockdownMode(addressPrefix, true); 38 | assertEq(evc.isLockdownMode(addressPrefix), true); 39 | 40 | vm.expectEmit(true, true, false, false, address(evc)); 41 | emit LockdownModeStatus(addressPrefix, false); 42 | 43 | vm.prank(alice); 44 | evc.setLockdownMode(addressPrefix, false); 45 | assertEq(evc.isLockdownMode(addressPrefix), false); 46 | 47 | vm.expectEmit(true, true, false, false, address(evc)); 48 | emit LockdownModeStatus(addressPrefix, true); 49 | 50 | vm.prank(alice); 51 | evc.setLockdownMode(addressPrefix, true); 52 | assertEq(evc.isLockdownMode(addressPrefix), true); 53 | } 54 | 55 | function test_RevertIfChecksDeferred_SetLockdownMode(address alice) public { 56 | vm.assume(alice != address(0) && alice != address(evc)); 57 | 58 | bytes19 addressPrefix = evc.getAddressPrefix(alice); 59 | 60 | // set checks deferred 61 | evc.setChecksDeferred(true); 62 | 63 | // succeeds with checks deferred when enabling 64 | vm.prank(alice); 65 | evc.setLockdownMode(addressPrefix, true); 66 | assertEq(evc.isLockdownMode(addressPrefix), true); 67 | 68 | // fails with checks deferred when disabling 69 | vm.prank(alice); 70 | vm.expectRevert(Errors.EVC_NotAuthorized.selector); 71 | evc.setLockdownMode(addressPrefix, false); 72 | } 73 | 74 | function test_Integration_SetLockdownMode(address alice, address vault, address operator) public { 75 | vm.assume(alice != address(0) && alice != address(evc) && !evc.haveCommonOwner(alice, operator)); 76 | vm.assume( 77 | vault != address(0) && vault != address(evc) && vault != address(this) && vault != alice 78 | && vault != operator && !evc.haveCommonOwner(vault, address(0)) && vault.code.length == 0 79 | ); 80 | vm.assume(operator != address(0) && operator != address(evc)); 81 | 82 | bytes19 addressPrefix = evc.getAddressPrefix(alice); 83 | 84 | // setting nonce works when not in lockdown mode 85 | vm.prank(alice); 86 | evc.setLockdownMode(addressPrefix, false); 87 | 88 | vm.prank(alice); 89 | evc.setNonce(addressPrefix, 0, 1); 90 | 91 | // setting nonce also works when in lockdown mode 92 | vm.prank(alice); 93 | evc.setLockdownMode(addressPrefix, true); 94 | 95 | vm.prank(alice); 96 | evc.setNonce(addressPrefix, 1, 1); 97 | 98 | // setting operator works when not in lockdown mode 99 | vm.prank(alice); 100 | evc.setLockdownMode(addressPrefix, false); 101 | 102 | vm.prank(alice); 103 | evc.setOperator(addressPrefix, operator, 1); 104 | 105 | // setting operator also works when in lockdown mode 106 | vm.prank(alice); 107 | evc.setLockdownMode(addressPrefix, true); 108 | 109 | vm.prank(alice); 110 | evc.setOperator(addressPrefix, operator, 2); 111 | 112 | // enabling collateral works when not in lockdown mode 113 | vm.prank(alice); 114 | evc.setLockdownMode(addressPrefix, false); 115 | 116 | vm.prank(alice); 117 | evc.enableCollateral(alice, vault); 118 | 119 | // enabling collateral doesn't work when in lockdown mode 120 | vm.prank(alice); 121 | evc.setLockdownMode(addressPrefix, true); 122 | 123 | vm.prank(alice); 124 | vm.expectRevert(Errors.EVC_LockdownMode.selector); 125 | evc.enableCollateral(alice, vault); 126 | 127 | // external contract call works when not in lockdown mode 128 | vm.prank(alice); 129 | evc.setLockdownMode(addressPrefix, false); 130 | 131 | address targetContract = address(new Target()); 132 | bytes memory data = abi.encodeWithSelector( 133 | Target(targetContract).callTest.selector, address(evc), address(evc), 0, alice, false 134 | ); 135 | 136 | vm.prank(alice); 137 | evc.call(targetContract, alice, 0, data); 138 | 139 | // external contract call doesn't work when in lockdown mode 140 | vm.prank(alice); 141 | evc.setLockdownMode(addressPrefix, true); 142 | 143 | vm.prank(alice); 144 | vm.expectRevert(Errors.EVC_LockdownMode.selector); 145 | evc.call(targetContract, alice, 0, data); 146 | 147 | // control collateral works when not in lockdown mode 148 | address controller = address(new Vault(evc)); 149 | 150 | vm.prank(alice); 151 | evc.setLockdownMode(addressPrefix, false); 152 | 153 | vm.prank(alice); 154 | evc.enableController(alice, controller); 155 | 156 | vm.prank(controller); 157 | evc.controlCollateral(vault, alice, 0, ""); 158 | 159 | // control collateral still works when in lockdown mode 160 | vm.prank(alice); 161 | evc.setLockdownMode(addressPrefix, true); 162 | 163 | vm.prank(controller); 164 | evc.controlCollateral(vault, alice, 0, ""); 165 | 166 | // setting permit disabled mode still works when in lockdown mode 167 | vm.prank(alice); 168 | evc.setLockdownMode(addressPrefix, true); 169 | 170 | vm.prank(alice); 171 | evc.setPermitDisabledMode(addressPrefix, true); 172 | 173 | vm.prank(alice); 174 | evc.setPermitDisabledMode(addressPrefix, false); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /test/unit/EthereumVaultConnector/SetNonce.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | import "../../evc/EthereumVaultConnectorHarness.sol"; 7 | 8 | contract SetNonceTest is Test { 9 | EthereumVaultConnectorHarness internal evc; 10 | 11 | event NonceStatus( 12 | bytes19 indexed addressPrefix, uint256 indexed nonceNamespace, uint256 oldNonce, uint256 newNonce 13 | ); 14 | 15 | function setUp() public { 16 | evc = new EthereumVaultConnectorHarness(); 17 | } 18 | 19 | function test_SetNonce(address alice, uint256 nonceNamespace, uint128 nonce) public { 20 | vm.assume(alice != address(0) && alice != address(evc)); 21 | vm.assume(nonce > 0); 22 | 23 | bytes19 addressPrefix = evc.getAddressPrefix(alice); 24 | assertEq(evc.getNonce(addressPrefix, nonceNamespace), 0); 25 | 26 | vm.expectEmit(true, true, false, true, address(evc)); 27 | emit NonceStatus(addressPrefix, nonceNamespace, 0, nonce); 28 | vm.prank(alice); 29 | evc.setNonce(addressPrefix, nonceNamespace, nonce); 30 | assertEq(evc.getNonce(addressPrefix, nonceNamespace), nonce); 31 | 32 | vm.expectEmit(true, true, false, true, address(evc)); 33 | emit NonceStatus(addressPrefix, nonceNamespace, nonce, 2 * uint256(nonce)); 34 | vm.prank(alice); 35 | evc.setNonce(addressPrefix, nonceNamespace, 2 * uint256(nonce)); 36 | assertEq(evc.getNonce(addressPrefix, nonceNamespace), 2 * uint256(nonce)); 37 | } 38 | 39 | function test_RevertIfSenderNotOwner_SetNonce( 40 | address alice, 41 | address operator, 42 | uint256 nonceNamespace, 43 | uint256 nonce 44 | ) public { 45 | bytes19 addressPrefix = evc.getAddressPrefix(alice); 46 | vm.assume(alice != address(0) && alice != address(evc)); 47 | vm.assume(addressPrefix != bytes19(type(uint152).max)); 48 | vm.assume(operator != address(0) && operator != address(evc)); 49 | vm.assume(!evc.haveCommonOwner(alice, operator)); 50 | vm.assume(nonce > 0); 51 | 52 | // fails if address prefix does not belong to an owner 53 | vm.prank(alice); 54 | vm.expectRevert(Errors.EVC_NotAuthorized.selector); 55 | evc.setNonce(bytes19(uint152(addressPrefix) + 1), nonceNamespace, nonce); 56 | 57 | // succeeds if address prefix belongs to an owner 58 | vm.prank(alice); 59 | evc.setNonce(addressPrefix, nonceNamespace, nonce); 60 | 61 | // fails if owner not consistent 62 | vm.prank(address(uint160(uint160(alice) ^ 1))); 63 | vm.expectRevert(Errors.EVC_NotAuthorized.selector); 64 | evc.setNonce(addressPrefix, nonceNamespace, nonce); 65 | 66 | // reverts if sender is an operator 67 | vm.prank(alice); 68 | evc.setAccountOperator(alice, operator, true); 69 | 70 | vm.prank(operator); 71 | vm.expectRevert(Errors.EVC_NotAuthorized.selector); 72 | evc.setNonce(addressPrefix, nonceNamespace, nonce); 73 | } 74 | 75 | function test_RevertIfInvalidNonce_SetNonce(address alice, uint256 nonceNamespace, uint256 nonce) public { 76 | vm.assume(alice != address(0) && alice != address(evc)); 77 | vm.assume(nonce > 0); 78 | 79 | bytes19 addressPrefix = evc.getAddressPrefix(alice); 80 | 81 | // fails if invalid nonce 82 | vm.prank(alice); 83 | vm.expectRevert(Errors.EVC_InvalidNonce.selector); 84 | evc.setNonce(addressPrefix, nonceNamespace, 0); 85 | 86 | // succeeds if valid nonce 87 | vm.prank(alice); 88 | evc.setNonce(addressPrefix, nonceNamespace, nonce); 89 | 90 | // fails again if invalid nonce 91 | vm.prank(alice); 92 | vm.expectRevert(Errors.EVC_InvalidNonce.selector); 93 | evc.setNonce(addressPrefix, nonceNamespace, nonce); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /test/unit/EthereumVaultConnector/SetPermitDisabledMode.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | import "../../evc/EthereumVaultConnectorHarness.sol"; 7 | 8 | contract SetPermitDisabledModeTest is Test { 9 | EthereumVaultConnectorHarness internal evc; 10 | 11 | event PermitDisabledModeStatus(bytes19 indexed addressPrefix, bool status); 12 | 13 | function setUp() public { 14 | evc = new EthereumVaultConnectorHarness(); 15 | } 16 | 17 | function test_SetPermitDisabledMode(address alice) public { 18 | vm.assume(alice != address(0) && alice != address(evc)); 19 | 20 | bytes19 addressPrefix = evc.getAddressPrefix(alice); 21 | assertEq(evc.isPermitDisabledMode(addressPrefix), false); 22 | 23 | // no-op when setting permit disabled mode to the same value 24 | vm.prank(alice); 25 | evc.setPermitDisabledMode(addressPrefix, false); 26 | assertEq(evc.isPermitDisabledMode(addressPrefix), false); 27 | 28 | vm.expectEmit(true, true, false, false, address(evc)); 29 | emit PermitDisabledModeStatus(addressPrefix, true); 30 | 31 | vm.prank(alice); 32 | evc.setPermitDisabledMode(addressPrefix, true); 33 | assertEq(evc.isPermitDisabledMode(addressPrefix), true); 34 | 35 | // no-op when setting permit disabled mode to the same value 36 | vm.prank(alice); 37 | evc.setPermitDisabledMode(addressPrefix, true); 38 | assertEq(evc.isPermitDisabledMode(addressPrefix), true); 39 | 40 | vm.expectEmit(true, true, false, false, address(evc)); 41 | emit PermitDisabledModeStatus(addressPrefix, false); 42 | 43 | vm.prank(alice); 44 | evc.setPermitDisabledMode(addressPrefix, false); 45 | assertEq(evc.isPermitDisabledMode(addressPrefix), false); 46 | 47 | vm.expectEmit(true, true, false, false, address(evc)); 48 | emit PermitDisabledModeStatus(addressPrefix, true); 49 | 50 | vm.prank(alice); 51 | evc.setPermitDisabledMode(addressPrefix, true); 52 | assertEq(evc.isPermitDisabledMode(addressPrefix), true); 53 | } 54 | 55 | function test_RevertIfChecksDeferred_SetPermitDisabledMode(address alice) public { 56 | vm.assume(alice != address(0) && alice != address(evc)); 57 | 58 | bytes19 addressPrefix = evc.getAddressPrefix(alice); 59 | 60 | // set checks deferred 61 | evc.setChecksDeferred(true); 62 | 63 | // succeeds with checks deferred when enabling 64 | vm.prank(alice); 65 | evc.setPermitDisabledMode(addressPrefix, true); 66 | assertEq(evc.isPermitDisabledMode(addressPrefix), true); 67 | 68 | // fails with checks deferred when disabling 69 | vm.prank(alice); 70 | vm.expectRevert(Errors.EVC_NotAuthorized.selector); 71 | evc.setPermitDisabledMode(addressPrefix, false); 72 | } 73 | 74 | function test_Integration_SetPermitDisabledMode(address alice, address vault, address operator) public { 75 | vm.assume(alice != address(0) && !evc.haveCommonOwner(alice, operator) && alice.code.length == 0); 76 | vm.assume( 77 | uint160(vault) > 255 && vault != alice && vault != operator && vault.code.length == 0 78 | && vault != 0xF62849F9A0B5Bf2913b396098F7c7019b51A820a 79 | ); 80 | vm.assume(operator != address(0) && operator.code.length == 0); 81 | 82 | bytes19 addressPrefix = evc.getAddressPrefix(alice); 83 | 84 | // setting nonce works when not in permit disabled mode 85 | vm.prank(alice); 86 | evc.setPermitDisabledMode(addressPrefix, false); 87 | 88 | vm.prank(alice); 89 | evc.setNonce(addressPrefix, 0, 1); 90 | 91 | // setting nonce also works when in permit disabled mode 92 | vm.prank(alice); 93 | evc.setPermitDisabledMode(addressPrefix, true); 94 | 95 | vm.prank(alice); 96 | evc.setNonce(addressPrefix, 1, 1); 97 | 98 | // setting operator works when not in permit disabled mode 99 | vm.prank(alice); 100 | evc.setPermitDisabledMode(addressPrefix, false); 101 | 102 | vm.prank(alice); 103 | evc.setOperator(addressPrefix, operator, 1); 104 | 105 | // setting operator also works when in permit disabled mode 106 | vm.prank(alice); 107 | evc.setPermitDisabledMode(addressPrefix, true); 108 | 109 | vm.prank(alice); 110 | evc.setOperator(addressPrefix, operator, 2); 111 | 112 | // enabling collateral works when not in permit disabled mode 113 | vm.prank(alice); 114 | evc.setPermitDisabledMode(addressPrefix, false); 115 | 116 | vm.prank(alice); 117 | evc.enableCollateral(alice, vault); 118 | 119 | // enabling collateral still works when in permit disabled mode 120 | vm.prank(alice); 121 | evc.setPermitDisabledMode(addressPrefix, true); 122 | 123 | vm.prank(alice); 124 | evc.enableCollateral(alice, vault); 125 | 126 | // external contract call works when not in permit disabled mode 127 | vm.prank(alice); 128 | evc.setPermitDisabledMode(addressPrefix, false); 129 | 130 | address targetContract = address(new Target()); 131 | bytes memory data = abi.encodeWithSelector( 132 | Target(targetContract).callTest.selector, address(evc), address(evc), 0, alice, false 133 | ); 134 | 135 | vm.prank(alice); 136 | evc.call(targetContract, alice, 0, data); 137 | 138 | // external contract call still works when in permit disabled mode 139 | vm.prank(alice); 140 | evc.setPermitDisabledMode(addressPrefix, true); 141 | 142 | vm.prank(alice); 143 | evc.call(targetContract, alice, 0, data); 144 | 145 | // control collateral works when not in permit disabled mode 146 | address controller = address(new Vault(evc)); 147 | 148 | vm.prank(alice); 149 | evc.setPermitDisabledMode(addressPrefix, false); 150 | 151 | vm.prank(alice); 152 | evc.enableController(alice, controller); 153 | 154 | vm.prank(controller); 155 | evc.controlCollateral(vault, alice, 0, ""); 156 | 157 | // control collateral still works when in permit disabled mode 158 | vm.prank(alice); 159 | evc.setPermitDisabledMode(addressPrefix, true); 160 | 161 | vm.prank(controller); 162 | evc.controlCollateral(vault, alice, 0, ""); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /test/unit/EthereumVaultConnector/VaultStatus.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Test.sol"; 6 | import "../../../src/Set.sol"; 7 | import "../../evc/EthereumVaultConnectorHarness.sol"; 8 | 9 | contract VaultStatusTest is Test { 10 | EthereumVaultConnectorHarness internal evc; 11 | 12 | event VaultStatusCheck(address indexed vault); 13 | 14 | function setUp() public { 15 | evc = new EthereumVaultConnectorHarness(); 16 | } 17 | 18 | function test_RequireVaultStatusCheck(uint8 vaultsNumber, bool allStatusesValid) external { 19 | vm.assume(vaultsNumber > 0 && vaultsNumber <= SET_MAX_ELEMENTS); 20 | 21 | for (uint256 i = 0; i < vaultsNumber; i++) { 22 | address vault = address(new Vault(evc)); 23 | 24 | // check all the options: vault state is ok, vault state is violated with 25 | // vault returning false and reverting 26 | Vault(vault).setVaultStatusState( 27 | allStatusesValid ? 0 : uint160(vault) % 3 == 0 ? 0 : uint160(vault) % 3 == 1 ? 1 : 2 28 | ); 29 | 30 | vm.prank(vault); 31 | if (allStatusesValid || uint160(vault) % 3 == 0) { 32 | vm.expectEmit(true, false, false, false, address(evc)); 33 | emit VaultStatusCheck(vault); 34 | } else { 35 | vm.expectRevert( 36 | uint160(vault) % 3 == 1 ? bytes("vault status violation") : abi.encode(bytes4(uint32(1))) 37 | ); 38 | } 39 | 40 | evc.requireVaultStatusCheck(); 41 | evc.verifyVaultStatusChecks(); 42 | evc.clearExpectedChecks(); 43 | } 44 | } 45 | 46 | function test_WhenDeferred_RequireVaultStatusCheck(uint8 vaultsNumber, bool allStatusesValid) external { 47 | vm.assume(vaultsNumber > 0 && vaultsNumber <= SET_MAX_ELEMENTS); 48 | 49 | for (uint256 i = 0; i < vaultsNumber; i++) { 50 | address vault = address(new Vault(evc)); 51 | 52 | // check all the options: vault state is ok, vault state is violated with 53 | // vault returning false and reverting 54 | Vault(vault).setVaultStatusState( 55 | allStatusesValid ? 0 : uint160(vault) % 3 == 0 ? 0 : uint160(vault) % 3 == 1 ? 1 : 2 56 | ); 57 | 58 | Vault(vault).setVaultStatusState(1); 59 | evc.setChecksDeferred(true); 60 | 61 | vm.prank(vault); 62 | 63 | // even though the vault status state was set to 1 which should revert, 64 | // it doesn't because in checks deferral we only add the vaults to the set 65 | // so that the checks can be performed later 66 | evc.requireVaultStatusCheck(); 67 | 68 | if (!(allStatusesValid || uint160(vault) % 3 == 0)) { 69 | // checks no longer deferred 70 | evc.setChecksDeferred(false); 71 | 72 | vm.prank(vault); 73 | vm.expectRevert(bytes("vault status violation")); 74 | evc.requireVaultStatusCheck(); 75 | } 76 | } 77 | } 78 | 79 | function test_RevertIfChecksReentrancy_RequireVaultStatusCheck(uint8 index, uint8 vaultsNumber) external { 80 | vm.assume(index < vaultsNumber); 81 | vm.assume(vaultsNumber > 0 && vaultsNumber <= SET_MAX_ELEMENTS); 82 | 83 | address[] memory vaults = new address[](vaultsNumber); 84 | for (uint256 i = 0; i < vaultsNumber; i++) { 85 | vaults[i] = address(new Vault(evc)); 86 | } 87 | 88 | evc.setChecksInProgress(true); 89 | 90 | vm.prank(vaults[index]); 91 | vm.expectRevert(abi.encodeWithSelector(Errors.EVC_ChecksReentrancy.selector)); 92 | evc.requireVaultStatusCheck(); 93 | 94 | evc.setChecksInProgress(false); 95 | vm.prank(vaults[index]); 96 | evc.requireVaultStatusCheck(); 97 | } 98 | 99 | function test_AcquireChecksLock_RequireVaultStatusChecks(uint8 numberOfVaults) external { 100 | vm.assume(numberOfVaults > 0 && numberOfVaults <= SET_MAX_ELEMENTS); 101 | 102 | address[] memory vaults = new address[](numberOfVaults); 103 | for (uint256 i = 0; i < numberOfVaults; i++) { 104 | vaults[i] = address(new VaultMalicious(evc)); 105 | 106 | VaultMalicious(vaults[i]).setExpectedErrorSelector(Errors.EVC_ChecksReentrancy.selector); 107 | 108 | vm.prank(vaults[i]); 109 | // function will revert with EVC_VaultStatusViolation according to VaultMalicious implementation 110 | vm.expectRevert(bytes("malicious vault")); 111 | evc.requireVaultStatusCheck(); 112 | } 113 | } 114 | 115 | function test_ForgiveVaultStatusCheck(uint8 vaultsNumber) external { 116 | vm.assume(vaultsNumber > 0 && vaultsNumber <= SET_MAX_ELEMENTS); 117 | 118 | for (uint256 i = 0; i < vaultsNumber; i++) { 119 | address vault = address(new Vault(evc)); 120 | 121 | // vault status check will be scheduled for later due to deferred state 122 | evc.setChecksDeferred(true); 123 | 124 | vm.prank(vault); 125 | evc.requireVaultStatusCheck(); 126 | 127 | assertTrue(evc.isVaultStatusCheckDeferred(vault)); 128 | vm.prank(vault); 129 | evc.forgiveVaultStatusCheck(); 130 | assertFalse(evc.isVaultStatusCheckDeferred(vault)); 131 | } 132 | } 133 | 134 | function test_RevertIfChecksReentrancy_ForgiveVaultStatusCheck(bool locked) external { 135 | evc.setChecksInProgress(locked); 136 | 137 | if (locked) { 138 | vm.expectRevert(abi.encodeWithSelector(Errors.EVC_ChecksReentrancy.selector)); 139 | } 140 | evc.forgiveVaultStatusCheck(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /test/utils/mocks/Target.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "../../../src/EthereumVaultConnector.sol"; 6 | 7 | // mock target contract that allows to test call() and controlCollateral() functions of the EVC 8 | contract Target { 9 | function callTest( 10 | address evc, 11 | address msgSender, 12 | uint256 value, 13 | address onBehalfOfAccount, 14 | bool operatorAuthenticated 15 | ) external payable returns (uint256) { 16 | try IEVC(evc).getCurrentOnBehalfOfAccount(address(0)) returns (address _onBehalfOfAccount, bool) { 17 | require(_onBehalfOfAccount == onBehalfOfAccount, "ct/invalid-on-behalf-of-account"); 18 | } catch { 19 | require(onBehalfOfAccount == address(0), "ct/invalid-on-behalf-of-account-2"); 20 | } 21 | require(msg.sender == msgSender, "ct/invalid-sender"); 22 | require(msg.value == value, "ct/invalid-msg-value"); 23 | require(IEVC(evc).areChecksDeferred(), "ct/invalid-checks-deferred"); 24 | require(!IEVC(evc).areChecksInProgress(), "ct/checks-lock"); 25 | require(!IEVC(evc).isControlCollateralInProgress(), "ct/controlCollateral-lock"); 26 | require( 27 | operatorAuthenticated ? IEVC(evc).isOperatorAuthenticated() : !IEVC(evc).isOperatorAuthenticated(), 28 | "ct/operator-authenticated" 29 | ); 30 | 31 | IEVC(evc).requireAccountStatusCheck(onBehalfOfAccount); 32 | require(IEVC(evc).isAccountStatusCheckDeferred(onBehalfOfAccount), "ct/account-status-checks-not-deferred"); 33 | return msg.value; 34 | } 35 | 36 | function controlCollateralTest( 37 | address evc, 38 | address msgSender, 39 | uint256 value, 40 | address onBehalfOfAccount 41 | ) external payable returns (uint256) { 42 | try IEVC(evc).getCurrentOnBehalfOfAccount(address(0)) returns (address _onBehalfOfAccount, bool) { 43 | require(_onBehalfOfAccount == onBehalfOfAccount, "it/invalid-on-behalf-of-account"); 44 | } catch { 45 | require(onBehalfOfAccount == address(0), "it/invalid-on-behalf-of-account-2"); 46 | } 47 | require(msg.sender == msgSender, "it/invalid-sender"); 48 | require(msg.value == value, "it/invalid-msg-value"); 49 | require(IEVC(evc).areChecksDeferred(), "it/invalid-checks-deferred"); 50 | require(!IEVC(evc).areChecksInProgress(), "it/checks-lock"); 51 | require(IEVC(evc).isControlCollateralInProgress(), "it/controlCollateral-lock"); 52 | 53 | IEVC(evc).requireAccountStatusCheck(onBehalfOfAccount); 54 | require(IEVC(evc).isAccountStatusCheckDeferred(onBehalfOfAccount), "it/account-status-checks-not-deferred"); 55 | 56 | return msg.value; 57 | } 58 | 59 | function callbackTest( 60 | address evc, 61 | address msgSender, 62 | uint256 value, 63 | address onBehalfOfAccount 64 | ) external payable returns (uint256) { 65 | try IEVC(evc).getCurrentOnBehalfOfAccount(address(0)) returns (address _onBehalfOfAccount, bool) { 66 | require(_onBehalfOfAccount == onBehalfOfAccount, "cbt/invalid-on-behalf-of-account"); 67 | } catch { 68 | require(onBehalfOfAccount == address(0), "cbt/invalid-on-behalf-of-account-2"); 69 | } 70 | require(msg.sender == msgSender, "cbt/invalid-sender"); 71 | require(msg.value == value, "ct/invalid-msg-value"); 72 | require(IEVC(evc).areChecksDeferred(), "cbt/invalid-checks-deferred"); 73 | 74 | require(!IEVC(evc).areChecksInProgress(), "cbt/controlCollateral-lock"); 75 | require(!IEVC(evc).isControlCollateralInProgress(), "cbt/controlCollateral-lock"); 76 | require(!IEVC(evc).isOperatorAuthenticated(), "cbt/operator-authenticated"); 77 | 78 | IEVC(evc).requireAccountStatusCheck(onBehalfOfAccount); 79 | require(IEVC(evc).isAccountStatusCheckDeferred(onBehalfOfAccount), "cbt/account-status-checks-not-deferred"); 80 | return msg.value; 81 | } 82 | 83 | function revertEmptyTest() external pure { 84 | revert(); 85 | } 86 | } 87 | 88 | contract TargetWithNesting { 89 | function nestedCallTest( 90 | address evc, 91 | address msgSender, 92 | address targetContract, 93 | uint256 value, 94 | address onBehalfOfAccount, 95 | bool operatorAuthenticated 96 | ) external payable returns (uint256) { 97 | try IEVC(evc).getCurrentOnBehalfOfAccount(address(0)) returns (address _onBehalfOfAccount, bool) { 98 | require(_onBehalfOfAccount == onBehalfOfAccount, "nct/invalid-on-behalf-of-account"); 99 | } catch { 100 | require(onBehalfOfAccount == address(0), "nct/invalid-on-behalf-of-account-2"); 101 | } 102 | require(msg.sender == msgSender, "nct/invalid-sender"); 103 | require(msg.value == value, "nct/invalid-msg-value"); 104 | require(IEVC(evc).areChecksDeferred(), "nct/invalid-checks-deferred"); 105 | require(!IEVC(evc).areChecksInProgress(), "nct/checks-lock"); 106 | require(!IEVC(evc).isControlCollateralInProgress(), "nct/controlCollateral-lock"); 107 | require( 108 | operatorAuthenticated ? IEVC(evc).isOperatorAuthenticated() : !IEVC(evc).isOperatorAuthenticated(), 109 | "nct/operator-authenticated" 110 | ); 111 | 112 | bytes memory result = IEVC(evc).call( 113 | targetContract, 114 | address(this), 115 | 0, 116 | abi.encodeWithSelector(Target.callTest.selector, evc, evc, 0, address(this), false, false) 117 | ); 118 | require(abi.decode(result, (uint256)) == 0, "nct/result"); 119 | 120 | try IEVC(evc).getCurrentOnBehalfOfAccount(address(0)) returns (address _onBehalfOfAccount, bool) { 121 | require(_onBehalfOfAccount == onBehalfOfAccount, "nct/invalid-on-behalf-of-account-3"); 122 | } catch { 123 | require(onBehalfOfAccount == address(0), "nct/invalid-on-behalf-of-account-4"); 124 | } 125 | require(IEVC(evc).areChecksDeferred(), "nct/invalid-checks-deferred-2"); 126 | require(!IEVC(evc).isControlCollateralInProgress(), "nct/controlCollateral-lock-2"); 127 | require( 128 | operatorAuthenticated ? IEVC(evc).isOperatorAuthenticated() : !IEVC(evc).isOperatorAuthenticated(), 129 | "nct/operator-authenticated-2" 130 | ); 131 | 132 | return msg.value; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /test/utils/mocks/Vault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "./Target.sol"; 6 | import "../../../src/interfaces/IVault.sol"; 7 | 8 | // mock vault contract that implements required interface and helps with status checks verification 9 | contract Vault is IVault, Target { 10 | IEVC public immutable evc; 11 | 12 | uint256 internal vaultStatusState; 13 | uint256 internal accountStatusState; 14 | 15 | bool[] internal vaultStatusChecked; 16 | address[] internal accountStatusChecked; 17 | 18 | constructor(IEVC _evc) { 19 | evc = _evc; 20 | } 21 | 22 | function reset() external { 23 | vaultStatusState = 0; 24 | accountStatusState = 0; 25 | delete vaultStatusChecked; 26 | delete accountStatusChecked; 27 | } 28 | 29 | function clearChecks() external { 30 | delete vaultStatusChecked; 31 | delete accountStatusChecked; 32 | } 33 | 34 | function setVaultStatusState(uint256 state) external { 35 | vaultStatusState = state; 36 | } 37 | 38 | function setAccountStatusState(uint256 state) external { 39 | accountStatusState = state; 40 | } 41 | 42 | function pushVaultStatusChecked() external { 43 | vaultStatusChecked.push(true); 44 | } 45 | 46 | function pushAccountStatusChecked(address account) external { 47 | accountStatusChecked.push(account); 48 | } 49 | 50 | function getVaultStatusChecked() external view returns (bool[] memory) { 51 | return vaultStatusChecked; 52 | } 53 | 54 | function getAccountStatusChecked() external view returns (address[] memory) { 55 | return accountStatusChecked; 56 | } 57 | 58 | function disableController() external virtual override { 59 | address msgSender = msg.sender; 60 | if (msgSender == address(evc)) { 61 | (address onBehalfOfAccount,) = evc.getCurrentOnBehalfOfAccount(address(0)); 62 | msgSender = onBehalfOfAccount; 63 | } 64 | 65 | evc.disableController(msgSender); 66 | } 67 | 68 | function checkVaultStatus() external virtual override returns (bytes4 magicValue) { 69 | try evc.getCurrentOnBehalfOfAccount(address(0)) { 70 | revert("cvs/on-behalf-of-account"); 71 | } catch (bytes memory reason) { 72 | if (bytes4(reason) != Errors.EVC_OnBehalfOfAccountNotAuthenticated.selector) { 73 | revert("cvs/on-behalf-of-account-2"); 74 | } 75 | } 76 | try evc.getLastAccountStatusCheckTimestamp(address(0)) { 77 | revert("cvs/last-account-status-check-timestamp"); 78 | } catch (bytes memory reason) { 79 | if (bytes4(reason) != Errors.EVC_ChecksReentrancy.selector) { 80 | revert("cvs/last-account-status-check-timestamp-2"); 81 | } 82 | } 83 | require(evc.areChecksInProgress(), "cvs/checks-not-in-progress"); 84 | 85 | if (vaultStatusState == 0) { 86 | return 0x4b3d1223; 87 | } else if (vaultStatusState == 1) { 88 | revert("vault status violation"); 89 | } else { 90 | return bytes4(uint32(1)); 91 | } 92 | } 93 | 94 | function checkAccountStatus(address, address[] memory) external view virtual override returns (bytes4 magicValue) { 95 | try evc.getCurrentOnBehalfOfAccount(address(0)) { 96 | revert("cas/on-behalf-of-account"); 97 | } catch (bytes memory reason) { 98 | if (bytes4(reason) != Errors.EVC_OnBehalfOfAccountNotAuthenticated.selector) { 99 | revert("cas/on-behalf-of-account-2"); 100 | } 101 | } 102 | try evc.getLastAccountStatusCheckTimestamp(address(0)) { 103 | revert("cas/last-account-status-check-timestamp"); 104 | } catch (bytes memory reason) { 105 | if (bytes4(reason) != Errors.EVC_ChecksReentrancy.selector) { 106 | revert("cas/last-account-status-check-timestamp-2"); 107 | } 108 | } 109 | require(evc.areChecksInProgress(), "cas/checks-not-in-progress"); 110 | 111 | if (accountStatusState == 0) { 112 | return 0xb168c58f; 113 | } else if (accountStatusState == 1) { 114 | revert("account status violation"); 115 | } else { 116 | return bytes4(uint32(2)); 117 | } 118 | } 119 | 120 | function requireChecks(address account) external payable { 121 | evc.requireAccountAndVaultStatusCheck(account); 122 | } 123 | 124 | function requireChecksWithSimulationCheck(address account, bool expectedSimulationInProgress) external payable { 125 | require( 126 | evc.isSimulationInProgress() == expectedSimulationInProgress, "requireChecksWithSimulationCheck/simulation" 127 | ); 128 | 129 | evc.requireAccountAndVaultStatusCheck(account); 130 | } 131 | 132 | function call(address target, bytes memory data) external payable virtual { 133 | (bool success,) = target.call{value: msg.value}(data); 134 | require(success, "call/failed"); 135 | } 136 | } 137 | 138 | contract VaultMalicious is Vault { 139 | bytes4 internal expectedErrorSelector; 140 | 141 | constructor(IEVC _evc) Vault(_evc) {} 142 | 143 | function setExpectedErrorSelector(bytes4 selector) external { 144 | expectedErrorSelector = selector; 145 | } 146 | 147 | function callBatch() external payable { 148 | (bool success, bytes memory result) = 149 | address(evc).call(abi.encodeWithSelector(evc.batch.selector, new IEVC.BatchItem[](0))); 150 | 151 | require(!success, "callBatch/succeeded"); 152 | if (bytes4(result) == expectedErrorSelector) { 153 | revert("callBatch/expected-error"); 154 | } 155 | } 156 | 157 | function checkVaultStatus() external virtual override returns (bytes4) { 158 | try evc.getCurrentOnBehalfOfAccount(address(0)) { 159 | revert("cvs/on-behalf-of-account"); 160 | } catch (bytes memory reason) { 161 | if (bytes4(reason) != Errors.EVC_OnBehalfOfAccountNotAuthenticated.selector) { 162 | revert("cvs/on-behalf-of-account-2"); 163 | } 164 | } 165 | try evc.getLastAccountStatusCheckTimestamp(address(0)) { 166 | revert("cvs/last-account-status-check-timestamp"); 167 | } catch (bytes memory reason) { 168 | if (bytes4(reason) != Errors.EVC_ChecksReentrancy.selector) { 169 | revert("cvs/last-account-status-check-timestamp-2"); 170 | } 171 | } 172 | require(evc.areChecksInProgress(), "cvs/checks-not-in-progress"); 173 | 174 | if (expectedErrorSelector == 0) { 175 | return this.checkVaultStatus.selector; 176 | } 177 | 178 | (bool success, bytes memory result) = 179 | address(evc).staticcall(abi.encodeWithSelector(evc.getLastAccountStatusCheckTimestamp.selector, address(0))); 180 | 181 | if (success || bytes4(result) != expectedErrorSelector) { 182 | return this.checkVaultStatus.selector; 183 | } 184 | 185 | revert("malicious vault"); 186 | } 187 | 188 | function checkAccountStatus(address, address[] memory) external view override returns (bytes4) { 189 | try evc.getCurrentOnBehalfOfAccount(address(0)) { 190 | revert("cas/on-behalf-of-account"); 191 | } catch (bytes memory reason) { 192 | if (bytes4(reason) != Errors.EVC_OnBehalfOfAccountNotAuthenticated.selector) { 193 | revert("cas/on-behalf-of-account-2"); 194 | } 195 | } 196 | try evc.getLastAccountStatusCheckTimestamp(address(0)) { 197 | revert("cas/last-account-status-check-timestamp"); 198 | } catch (bytes memory reason) { 199 | if (bytes4(reason) != Errors.EVC_ChecksReentrancy.selector) { 200 | revert("cas/last-account-status-check-timestamp-2"); 201 | } 202 | } 203 | require(evc.areChecksInProgress(), "cas/checks-not-in-progress"); 204 | 205 | if (expectedErrorSelector == 0) { 206 | return this.checkAccountStatus.selector; 207 | } 208 | 209 | (bool success, bytes memory result) = 210 | address(evc).staticcall(abi.encodeWithSelector(evc.getLastAccountStatusCheckTimestamp.selector, address(0))); 211 | 212 | if (success || bytes4(result) != expectedErrorSelector) { 213 | return this.checkAccountStatus.selector; 214 | } 215 | 216 | revert("malicious vault"); 217 | } 218 | } 219 | --------------------------------------------------------------------------------