├── .devcontainer ├── devcontainer.json └── postCreate.sh ├── .git-blame-ignore-revs ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── pre-commit.yml │ ├── publish-halmos-builder-package.yml │ ├── publish-halmos-package.yml │ ├── publish-pypi.yml │ ├── publish-solvers-package.yml │ ├── test-external.yml │ ├── test-ffi.yml │ ├── test-long.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── benchmarks └── baolean.sh ├── docs └── getting-started.md ├── examples ├── README.md ├── simple │ ├── README.md │ ├── foundry.toml │ ├── remappings.txt │ ├── src │ │ ├── BadElections.sol │ │ ├── IsPowerOfTwo.sol │ │ ├── MiniVat.sol │ │ ├── TotalPrice.sol │ │ ├── Vat.sol │ │ ├── Vault.sol │ │ └── multicaller │ │ │ └── MulticallerWithSender.sol │ └── test │ │ ├── BadElections.t.sol │ │ ├── Fork.t.sol │ │ ├── IsPowerOfTwo.t.sol │ │ ├── MiniVat.t.sol │ │ ├── Multicaller.t.sol │ │ ├── Reentrancy.t.sol │ │ ├── SimpleState.t.sol │ │ ├── TotalPrice.t.sol │ │ ├── Vat.t.sol │ │ └── Vault.t.sol └── tokens │ ├── ERC20 │ ├── foundry.toml │ ├── remappings.txt │ ├── src │ │ ├── BackdoorERC20.sol │ │ ├── OpenZeppelinERC20.sol │ │ ├── SoladyERC20.sol │ │ └── SolmateERC20.sol │ └── test │ │ ├── BackdoorERC20.t.sol │ │ ├── CurveTokenV3.t.sol │ │ ├── DEIStablecoin.sol │ │ ├── DEIStablecoin.t.sol │ │ ├── ERC20Test.sol │ │ ├── OpenZeppelinERC20.t.sol │ │ ├── SoladyERC20.t.sol │ │ └── SolmateERC20.t.sol │ └── ERC721 │ ├── foundry.toml │ ├── remappings.txt │ ├── src │ ├── OpenZeppelinERC721.sol │ ├── SoladyERC721.sol │ └── SolmateERC721.sol │ └── test │ ├── ERC721Test.sol │ ├── OpenZeppelinERC721.t.sol │ ├── SoladyERC721.t.sol │ └── SolmateERC721.t.sol ├── packages ├── halmos-builder │ └── Dockerfile ├── halmos │ ├── Dockerfile │ └── README.md └── solvers │ ├── .dockerignore │ ├── Dockerfile │ └── README.md ├── pyproject.toml ├── requirements-benchmark.txt ├── src └── halmos │ ├── __init__.py │ ├── __main__.py │ ├── assertions.py │ ├── bitvec.py │ ├── build.py │ ├── bytevec.py │ ├── calldata.py │ ├── cheatcodes.py │ ├── config.py │ ├── console.py │ ├── constants.py │ ├── contract.py │ ├── exceptions.py │ ├── flamegraphs.py │ ├── hashes.py │ ├── logs.py │ ├── mapper.py │ ├── memtrace.py │ ├── processes.py │ ├── sevm.py │ ├── solve.py │ ├── solvers.py │ ├── traces.py │ ├── ui.py │ └── utils.py └── tests ├── conftest.py ├── data └── multi-contract-ast.json ├── expected ├── all.json ├── erc20.json ├── erc721.json ├── ffi.json ├── simple.json └── solver.json ├── ffi ├── foundry.toml ├── remappings.txt └── test │ └── Ffi.t.sol ├── regression ├── foundry.toml ├── halmos.toml ├── remappings.txt ├── src │ ├── Const.sol │ ├── Counter.sol │ ├── Create.sol │ ├── List.sol │ ├── SignExtend.sol │ └── Storage.sol └── test │ ├── Arith.t.sol │ ├── AssertTest.t.sol │ ├── Block.t.sol │ ├── BlockNumber.t.sol │ ├── Buffers.t.sol │ ├── Byte.t.sol │ ├── Call.t.sol │ ├── CallAlias.t.sol │ ├── Concretization.t.sol │ ├── Console.t.sol │ ├── Const.t.sol │ ├── Constructor.t.sol │ ├── Context.t.sol │ ├── Counter.t.sol │ ├── Create.t.sol │ ├── Create2.t.sol │ ├── Deal.t.sol │ ├── Extcodehash.t.sol │ ├── Foundry.t.sol │ ├── Getter.t.sol │ ├── HalmosCheatCode.t.sol │ ├── Invalid.t.sol │ ├── Invariant.t.sol │ ├── InvariantProbes.t.sol │ ├── InvariantSender.t.sol │ ├── InvariantSender2.t.sol │ ├── InvariantTarget.t.sol │ ├── InvariantTarget2.t.sol │ ├── InvariantTimestamp.t.sol │ ├── Library.t.sol │ ├── LibraryLinking.t.sol │ ├── List.t.sol │ ├── MegaMem.t.sol │ ├── Natspec.t.sol │ ├── Opcode.t.sol │ ├── Panic.t.sol │ ├── Prank.t.sol │ ├── Proxy.t.sol │ ├── Revert.t.sol │ ├── Send.t.sol │ ├── Setup.t.sol │ ├── SetupPlus.t.sol │ ├── SetupSymbolic.t.sol │ ├── Sha3.t.sol │ ├── SignExtend.t.sol │ ├── Signature.t.sol │ ├── SmolWETH.t.sol │ ├── Snapshot.t.sol │ ├── Solver.t.sol │ ├── SortMismatch.t.sol │ ├── StaticContexts.t.sol │ ├── StdAssertTest.t.sol │ ├── Storage.t.sol │ ├── Storage2.t.sol │ ├── Storage3.t.sol │ ├── Storage4.t.sol │ ├── StorageSlot.t.sol │ ├── Store.t.sol │ ├── Struct.t.sol │ ├── SymbolicCall.t.sol │ ├── TStore.t.sol │ ├── TestConstructor.t.sol │ ├── Token.t.sol │ ├── UniswapPairAddress.t.sol │ ├── UnknownCall.t.sol │ ├── UnsupportedOpcode.t.sol │ └── Warp.t.sol ├── solver ├── foundry.toml ├── remappings.txt └── test │ ├── Math.t.sol │ ├── SignedDiv.t.sol │ └── Solver.t.sol ├── test_bitvec.py ├── test_bytevec.py ├── test_cli.py ├── test_config.py ├── test_fixtures.py ├── test_halmos.py ├── test_mapper.py ├── test_prank.py ├── test_sevm.py ├── test_solve.py ├── test_source_map.py ├── test_traces.py └── test_utils.py /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "mcr.microsoft.com/devcontainers/python:3.12", 3 | "postCreateCommand": "/bin/bash .devcontainer/postCreate.sh" 4 | } -------------------------------------------------------------------------------- /.devcontainer/postCreate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python -m venv .venv 3 | source .venv/bin/activate 4 | python -m pip install -e .[dev] 5 | pre-commit install 6 | pre-commit run --all-files 7 | 8 | # Installing during postCreateCommand instead of at docker creation 9 | # because foundryup installs the binaries in place not accessible by the 10 | # current (vscode) user. 11 | curl -L https://foundry.paradigm.xyz | bash 12 | /home/vscode/.foundry/bin/foundryup 13 | 14 | echo 'source .venv/bin/activate' >> ~/.bashrc 15 | 16 | # just a test 17 | # pytest -v -k "not long and not ffi" --ignore=tests/lib --halmos-options="-v -st --storage-layout solidity --solver-timeout-assertion 0" 18 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Migrate code style to Black 2 | 449404c7a2b318b8203dbccbeac11c87d20c422f 3 | 00cc14c20659cbf717594451dfc7906b295dca98 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Please provide a reproducible example. 15 | 16 | **Environment:** 17 | - OS: [e.g., Linux / Windows / macOS] 18 | - Python version: [e.g., `python --version`] 19 | - Halmos and other dependency versions: [e.g., `pip list`] 20 | 21 | **Additional context** 22 | Add any other context about the problem here. 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | pre-commit: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: actions/setup-python@v5 11 | - uses: pre-commit/action@v3.0.1 12 | -------------------------------------------------------------------------------- /.github/workflows/publish-halmos-builder-package.yml: -------------------------------------------------------------------------------- 1 | # based on https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-a-registry-using-a-personal-access-token 2 | name: Push halmos-builder package 3 | 4 | on: 5 | # only trigger manually, this is independent from halmos releases 6 | workflow_dispatch: 7 | workflow_run: 8 | workflows: ["Push solvers package"] 9 | types: 10 | - completed 11 | push: 12 | paths: 13 | - packages/halmos-builder/**/* 14 | - .github/workflows/publish-halmos-builder-package.yml 15 | 16 | env: 17 | IMAGE_NAME: halmos-builder 18 | 19 | jobs: 20 | # This pushes the image to GitHub Packages. 21 | push: 22 | # if triggered by a workflow_run, only run if the previous workflow succeeded 23 | if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name != 'workflow_run' }} 24 | runs-on: ubuntu-latest 25 | permissions: 26 | packages: write 27 | contents: read 28 | 29 | steps: 30 | - name: Login to GitHub Container Registry 31 | uses: docker/login-action@v3 32 | with: 33 | registry: ghcr.io 34 | username: ${{ github.actor }} 35 | password: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | - uses: actions/checkout@v4 38 | with: 39 | submodules: false 40 | 41 | - name: Build image 42 | run: docker build . --file packages/halmos-builder/Dockerfile --tag $IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}" 43 | 44 | - name: Push image 45 | run: | 46 | IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME 47 | 48 | # This changes all uppercase characters to lowercase. 49 | IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') 50 | 51 | # This strips the git ref prefix from the version. 52 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') 53 | 54 | # This strips the "v" prefix from the tag name. 55 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') 56 | 57 | # This uses the Docker `latest` tag convention. 58 | [ "$VERSION" == "main" ] && VERSION=latest 59 | echo IMAGE_ID=$IMAGE_ID 60 | echo VERSION=$VERSION 61 | docker tag $IMAGE_NAME $IMAGE_ID:$VERSION 62 | docker push $IMAGE_ID:$VERSION 63 | -------------------------------------------------------------------------------- /.github/workflows/publish-halmos-package.yml: -------------------------------------------------------------------------------- 1 | # based on https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-a-registry-using-a-personal-access-token 2 | name: Push halmos package 3 | 4 | on: 5 | workflow_dispatch: 6 | workflow_run: 7 | workflows: ["Push halmos-builder package"] 8 | types: 9 | - completed 10 | push: 11 | paths: 12 | - packages/halmos/**/* 13 | - .github/workflows/publish-halmos-package.yml 14 | tags: 15 | # push when we release a new tagged version 16 | - 'v*' 17 | 18 | env: 19 | IMAGE_NAME: halmos 20 | 21 | jobs: 22 | # This pushes the image to GitHub Packages. 23 | push: 24 | # if triggered by a workflow_run, only run if the previous workflow succeeded 25 | if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name != 'workflow_run' }} 26 | runs-on: ubuntu-latest 27 | permissions: 28 | packages: write 29 | contents: read 30 | 31 | steps: 32 | - name: Login to GitHub Container Registry 33 | uses: docker/login-action@v3 34 | with: 35 | registry: ghcr.io 36 | username: ${{ github.actor }} 37 | password: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - uses: actions/checkout@v4 40 | with: 41 | # we may need tests/lib for the workflows using this image 42 | submodules: recursive 43 | 44 | - name: Build image 45 | run: docker build . --file packages/halmos/Dockerfile --tag $IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}" 46 | 47 | - name: Print halmos version 48 | run: docker run $IMAGE_NAME halmos --version 49 | 50 | - name: Push image 51 | run: | 52 | IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME 53 | 54 | # This changes all uppercase characters to lowercase. 55 | IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') 56 | 57 | # This strips the git ref prefix from the version. 58 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') 59 | 60 | # This strips the "v" prefix from the tag name. 61 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') 62 | 63 | # This uses the Docker `latest` tag convention. 64 | [ "$VERSION" == "main" ] && VERSION=latest 65 | echo IMAGE_ID=$IMAGE_ID 66 | echo VERSION=$VERSION 67 | docker tag $IMAGE_NAME $IMAGE_ID:$VERSION 68 | docker push $IMAGE_ID:$VERSION 69 | -------------------------------------------------------------------------------- /.github/workflows/publish-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | pypi: 7 | runs-on: ubuntu-latest 8 | environment: release 9 | permissions: 10 | # IMPORTANT: this permission is mandatory for Trusted Publishing 11 | # https://docs.pypi.org/trusted-publishers/using-a-publisher/ 12 | id-token: write 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | - name: Build 19 | run: python3 -m pip install --upgrade build && python3 -m build 20 | - name: Publish 21 | uses: pypa/gh-action-pypi-publish@release/v1 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/publish-solvers-package.yml: -------------------------------------------------------------------------------- 1 | # based on https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-a-registry-using-a-personal-access-token 2 | name: Push solvers package 3 | 4 | on: 5 | # only trigger manually, this is independent from halmos releases 6 | workflow_dispatch: 7 | push: 8 | paths: 9 | - packages/solvers/**/* 10 | - .github/workflows/publish-solvers-package.yml 11 | env: 12 | IMAGE_NAME: solvers 13 | 14 | jobs: 15 | # This pushes the image to GitHub Packages. 16 | push: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | packages: write 20 | contents: read 21 | 22 | steps: 23 | - name: Login to GitHub Container Registry 24 | uses: docker/login-action@v3 25 | with: 26 | registry: ghcr.io 27 | username: ${{ github.actor }} 28 | password: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | - uses: actions/checkout@v4 31 | with: 32 | submodules: false 33 | 34 | - name: Build image 35 | run: docker build . --file packages/solvers/Dockerfile --tag $IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}" 36 | 37 | - name: Push image 38 | run: | 39 | IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME 40 | 41 | # This changes all uppercase characters to lowercase. 42 | IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') 43 | 44 | # This strips the git ref prefix from the version. 45 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') 46 | 47 | # This strips the "v" prefix from the tag name. 48 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') 49 | 50 | # This uses the Docker `latest` tag convention. 51 | [ "$VERSION" == "main" ] && VERSION=latest 52 | echo IMAGE_ID=$IMAGE_ID 53 | echo VERSION=$VERSION 54 | docker tag $IMAGE_NAME $IMAGE_ID:$VERSION 55 | docker push $IMAGE_ID:$VERSION 56 | -------------------------------------------------------------------------------- /.github/workflows/test-ffi.yml: -------------------------------------------------------------------------------- 1 | name: Test FFI 2 | 3 | on: 4 | push: 5 | branches: [main, chore-workflows] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | include: 18 | - testname: "tests/ffi" 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | submodules: recursive 24 | 25 | - name: Build image 26 | run: | 27 | docker build -t halmos-image . --file packages/halmos/Dockerfile 28 | 29 | - name: Print halmos version 30 | run: docker run halmos-image halmos --version 31 | 32 | - name: Run pytest 33 | run: docker run -v .:/workspace halmos-image pytest -v tests/test_halmos.py -k ${{ matrix.testname }} --halmos-options="--ffi -v -st --solver-timeout-assertion 0" 34 | -------------------------------------------------------------------------------- /.github/workflows/test-long.yml: -------------------------------------------------------------------------------- 1 | name: Test long 2 | 3 | on: 4 | push: 5 | branches: [main, chore-workflows] 6 | workflow_dispatch: 7 | inputs: 8 | halmos-options: 9 | description: "additional halmos options" 10 | required: false 11 | type: string 12 | default: "" 13 | 14 | jobs: 15 | test: 16 | runs-on: ubuntu-latest 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | cache-solver: ["", "--cache-solver"] 22 | testname: 23 | - "tests/solver" 24 | - "examples/simple" 25 | - "examples/tokens/ERC20" 26 | - "examples/tokens/ERC721" 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v4 31 | with: 32 | submodules: recursive 33 | 34 | - name: Build image 35 | run: | 36 | docker build -t halmos-image . --file packages/halmos/Dockerfile 37 | 38 | - name: Print halmos version 39 | run: docker run halmos-image halmos --version 40 | 41 | - name: Run pytest 42 | run: | 43 | docker run -v .:/workspace halmos-image pytest -x -v tests/test_halmos.py -k ${{ matrix.testname }} --halmos-options='-st --solver-timeout-assertion 0 --solver-threads 3 --solver-command yices-smt2 ${{ matrix.cache-solver }} ${{ inputs.halmos-options }}' -s --log-cli-level= 44 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [main, chore-workflows] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | inputs: 10 | halmos-options: 11 | description: "additional halmos options" 12 | required: false 13 | type: string 14 | default: "" 15 | pytest-options: 16 | description: "additional pytest options" 17 | required: false 18 | type: string 19 | default: "" 20 | 21 | jobs: 22 | test: 23 | runs-on: ${{ matrix.os }} 24 | env: 25 | HALMOS_ALLOW_DOWNLOAD: 1 26 | 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | os: ["macos-latest", "ubuntu-latest", "windows-latest"] 31 | python-version: ["3.11", "3.12", "3.13"] 32 | storage-layout: ["solidity", "generic"] 33 | cache-solver: ["", "--cache-solver"] 34 | 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@v4 38 | with: 39 | # note: it is faster to let foundry do a sparse checkout of the missing libraries rather than having the action do a git checkout of the submodules 40 | submodules: false 41 | 42 | - name: Install foundry 43 | uses: foundry-rs/foundry-toolchain@v1 44 | 45 | - name: Print foundry version 46 | run: forge --version 47 | 48 | - name: Install uv 49 | uses: astral-sh/setup-uv@v4 50 | with: 51 | # Install a specific version of uv. 52 | version: "0.5.21" 53 | 54 | - name: Set up python ${{ matrix.python-version }} 55 | run: uv python install ${{ matrix.python-version }} 56 | 57 | - name: Install halmos and its dependencies 58 | run: | 59 | uv sync --extra dev 60 | 61 | - name: Run pytest 62 | run: uv run pytest -v -k "not long and not ffi" --ignore=tests/lib --halmos-options="-st --disable-gc --solver-threads 1 --storage-layout ${{ matrix.storage-layout }} ${{ matrix.cache-solver }} --solver-timeout-assertion 0 ${{ inputs.halmos-options }}" ${{ inputs.pytest-options }} 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized 2 | __pycache__/ 3 | 4 | # Distribution / packaging 5 | *.egg-info/ 6 | 7 | # Pyre type checker 8 | .pyre/ 9 | 10 | # Vim tmp files: 11 | *~ 12 | *.swp 13 | *.swo 14 | 15 | # Environments 16 | .env 17 | .venv* 18 | env/ 19 | venv/ 20 | 21 | # Foundry build files 22 | cache/ 23 | out/ 24 | 25 | # VS Code 26 | .vscode/ 27 | 28 | # https://docs.astral.sh/uv/concepts/projects/layout/#the-lockfile 29 | # we go against the recommended best practice and don't add it to source control: 30 | # - adds too much noise 31 | # - adds friction to CI 32 | # (at the cost of reproducible builds) 33 | uv.lock 34 | 35 | # https://docs.astral.sh/uv/concepts/projects/layout/#the-build-directory 36 | build/ 37 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/lib/forge-std"] 2 | path = tests/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "tests/lib/halmos-cheatcodes"] 5 | path = tests/lib/halmos-cheatcodes 6 | url = https://github.com/a16z/halmos-cheatcodes 7 | [submodule "tests/lib/openzeppelin-contracts"] 8 | path = tests/lib/openzeppelin-contracts 9 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 10 | [submodule "tests/lib/solmate"] 11 | path = tests/lib/solmate 12 | url = https://github.com/transmissions11/solmate 13 | [submodule "tests/lib/solady"] 14 | path = tests/lib/solady 15 | url = https://github.com/Vectorized/solady 16 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | 3 | - repo: https://github.com/astral-sh/ruff-pre-commit 4 | # Ruff version. 5 | rev: v0.6.2 6 | hooks: 7 | # Run the linter. 8 | - id: ruff 9 | # don't autofix by default, this runs in CI 10 | # to run fix locally, use `ruff check src/ --fix` 11 | # args: [ --fix ] 12 | # Run the formatter. 13 | - id: ruff-format 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Halmos 2 | 3 | We greatly appreciate your feedback, suggestions, and contributions to make Halmos a better tool for everyone! 4 | 5 | Join the [Halmos Telegram Group][chat] for any inquiries or further discussions. 6 | 7 | [chat]: 8 | 9 | ## Development Setup 10 | 11 | Clone or fork the repository: 12 | 13 | ```sh 14 | # if you want to submit a pull request, fork the repository: 15 | gh repo fork a16z/halmos 16 | 17 | # Or, if you just want to develop locally, clone it: 18 | git clone git@github.com:a16z/halmos.git 19 | 20 | # navigate to the project directory 21 | cd halmos 22 | ``` 23 | 24 | **Recommended**: set up the development environment using [uv](https://docs.astral.sh/uv/): 25 | 26 | ```sh 27 | # install uv 28 | curl -LsSf https://astral.sh/uv/install.sh | sh 29 | 30 | # this does a lot of things: 31 | # - install a suitable python version if one is not found 32 | # - create a virtual environment in `.venv` 33 | # - install the main dependencies 34 | # - install the development dependencies 35 | # - generates a `uv.lock` file 36 | uv sync --extra dev 37 | 38 | # install and run the pre-commit hooks 39 | uv run pre-commit install 40 | uv run pre-commit run --all-files 41 | 42 | # make changes to halmos, then run it with: 43 | uv run halmos 44 | 45 | # run the tests with: 46 | uv run pytest 47 | 48 | # add a dependency to the project: 49 | uv add 50 | 51 | # remove a dependency from the project: 52 | uv remove 53 | 54 | # update a dependency to the latest version: 55 | uv lock --upgrade-package 56 | 57 | # to manually update the environment and activate it: 58 | uv sync 59 | source .venv/bin/activate 60 | ``` 61 | 62 | Alternatively, you can manage the python version and the virtual environment manually using `pip` (not recommended for most users): 63 | 64 | ```sh 65 | # create and activate a virtual environment with a suitable python version 66 | python3.12 -m venv .venv && source .venv/bin/activate 67 | 68 | # install halmos and its runtime dependencies in editable mode 69 | python -m pip install -e ".[dev]" 70 | 71 | # install and run the pre-commit hooks 72 | pre-commit install 73 | pre-commit run --all-files 74 | ``` 75 | 76 | 77 | ## Coding Style 78 | 79 | We recommend enabling the [ruff] formatter in your editor, but you can run it manually if needed: 80 | 81 | ```sh 82 | python -m ruff check src/ 83 | ``` 84 | 85 | [ruff]: 86 | 87 | ## GitHub Codespace 88 | 89 | A pre-configured development environment is available as a GitHub Codespaces dev container. 90 | 91 | ## License 92 | 93 | By contributing, you agree that your contributions will be licensed under its [AGPL-3.0](LICENSE) License. 94 | 95 | ## Disclaimer 96 | 97 | _These smart contracts and code are being provided as is. No guarantee, representation or warranty is being made, express or implied, as to the safety or correctness of the user interface or the smart contracts and code. They have not been audited and as such there can be no assurance they will work as intended, and users may experience delays, failures, errors, omissions or loss of transmitted information. THE SMART CONTRACTS AND CODE CONTAINED HEREIN ARE FURNISHED AS IS, WHERE IS, WITH ALL FAULTS AND WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING ANY WARRANTY OF MERCHANTABILITY, NON-INFRINGEMENT OR FITNESS FOR ANY PARTICULAR PURPOSE. Further, use of any of these smart contracts and code may be restricted or prohibited under applicable law, including securities laws, and it is therefore strongly advised for you to contact a reputable attorney in any jurisdiction where these smart contracts and code may be accessible for any questions or concerns with respect thereto. Further, no information provided in this repo should be construed as investment advice or legal advice for any particular facts or circumstances, and is not meant to replace competent counsel. a16z is not liable for any use of the foregoing, and users should proceed with caution and use at their own risk. See a16z.com/disclosures for more info._ 98 | -------------------------------------------------------------------------------- /benchmarks/baolean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | GREEN='\033[0;32m' 4 | NC='\033[0m' # No Color 5 | 6 | if [ ! -d "symexec-bench" ]; then 7 | echo "Cloning symexec-bench..." 8 | git clone --depth 1 -b symtest --single-branch https://github.com/baolean/symexec-bench.git 9 | else 10 | echo "Using existing symexec-bench checkout" 11 | fi 12 | 13 | for test_name in "PostExampleTest" "PostExampleTwoTest" "PostExampleTwoLiveTest" "FooTest" "MiniVatTest"; do 14 | echo 15 | echo -e "▀▄▀▄▀▄ 🎀 Running ${GREEN}${test_name}${NC} 🎀 ▄▀▄▀▄▀" 16 | time halmos --root symexec-bench/SymTest --contract ${test_name} "--function" test 17 | done 18 | 19 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Halmos Examples 2 | 3 | #### Usage Examples 4 | 5 | - [Simple examples](simple/test/) 6 | - [ERC20](tokens/ERC20/test/): verifies OpenZeppelin, Solady, Solmate ERC20 tokens, and CurveTokenV3. 7 | - Includes identifying the DEI token bug exploited in the [Deus DAO hack](https://rekt.news/deus-dao-r3kt/). 8 | - [ERC721](tokens/ERC721/test/): verifies OpenZeppelin, Solady, and Solmate ERC721 tokens. 9 | 10 | #### Halmos Tests in External Projects 11 | 12 | - [Morpho Blue] ([HalmosTest]): verifies the Morpho Blue protocol. 13 | - [Farcaster] ([IdRegistrySymTest], [KeyRegistrySymTest]): verifies state machine invariants in Farcaster's onchain registry contracts. 14 | - [Snekmate] ([ERC20TestHalmos], [ERC721TestHalmos], [ERC1155TestHalmos]): verifies Snekmate's Vyper token contracts. 15 | - [Cicada] ([LibPrimeTest], [LibUint1024Test]): verifies Cicada's 1024-bit number arithmetic library. 16 | - [Solady Verification]: verifies Solady's fixed-point math library. 17 | 18 | [Morpho Blue]: 19 | [HalmosTest]: 20 | 21 | [Snekmate]: 22 | [ERC20TestHalmos]: 23 | [ERC721TestHalmos]: 24 | [ERC1155TestHalmos]: 25 | 26 | [Cicada]: 27 | [LibPrimeTest]: 28 | [LibUint1024Test]: 29 | 30 | [Farcaster]: 31 | [IdRegistrySymTest]: 32 | [KeyRegistrySymTest]: 33 | 34 | [Solady Verification]: 35 | 36 | ## Disclaimer 37 | 38 | _These smart contracts and code are being provided as is. No guarantee, representation or warranty is being made, express or implied, as to the safety or correctness of the user interface or the smart contracts and code. They have not been audited and as such there can be no assurance they will work as intended, and users may experience delays, failures, errors, omissions or loss of transmitted information. THE SMART CONTRACTS AND CODE CONTAINED HEREIN ARE FURNISHED AS IS, WHERE IS, WITH ALL FAULTS AND WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING ANY WARRANTY OF MERCHANTABILITY, NON-INFRINGEMENT OR FITNESS FOR ANY PARTICULAR PURPOSE. Further, use of any of these smart contracts and code may be restricted or prohibited under applicable law, including securities laws, and it is therefore strongly advised for you to contact a reputable attorney in any jurisdiction where these smart contracts and code may be accessible for any questions or concerns with respect thereto. Further, no information provided in this repo should be construed as investment advice or legal advice for any particular facts or circumstances, and is not meant to replace competent counsel. a16z is not liable for any use of the foregoing, and users should proceed with caution and use at their own risk. See a16z.com/disclosures for more info._ 39 | -------------------------------------------------------------------------------- /examples/simple/README.md: -------------------------------------------------------------------------------- 1 | # Simple Examples 2 | 3 | Given a contract: 4 | ```solidity 5 | contract Example { 6 | function totalPriceBuggy(uint96 price, uint32 quantity) public pure returns (uint128) { 7 | unchecked { 8 | return uint120(price) * quantity; // buggy type casting: uint120 vs uint128 9 | } 10 | } 11 | } 12 | ``` 13 | 14 | You write some **property-based tests** (in Solidity): 15 | ```solidity 16 | contract ExampleTest is Example { 17 | function testTotalPriceBuggy(uint96 price, uint32 quantity) public pure { 18 | uint128 total = totalPriceBuggy(price, quantity); 19 | assert(quantity == 0 || total >= price); 20 | } 21 | } 22 | ``` 23 | 24 | Then you can run **fuzz testing** to quickly check those properties for **some random inputs**: 25 | ``` 26 | $ forge test 27 | [PASS] testTotalPriceBuggy(uint96,uint32) (runs: 256, μ: 462, ~: 466) 28 | ``` 29 | 30 | Once it passes, you can also perform **symbolic testing** to verify the same properties for **all possible inputs** (up to a specified limit): 31 | ``` 32 | $ halmos --function test 33 | [FAIL] testTotalPriceBuggy(uint96,uint32) (paths: 6, time: 0.10s, bounds: []) 34 | Counterexample: [p_price_uint96 = 39614081294025656978550816768, p_quantity_uint32 = 1073741824] 35 | ``` 36 | 37 | _(In this specific example, Halmos discovered an input that violated the assertion, which was missed by the fuzzer!)_ 38 | 39 | ## Disclaimer 40 | 41 | _These smart contracts and code are being provided as is. No guarantee, representation or warranty is being made, express or implied, as to the safety or correctness of the user interface or the smart contracts and code. They have not been audited and as such there can be no assurance they will work as intended, and users may experience delays, failures, errors, omissions or loss of transmitted information. THE SMART CONTRACTS AND CODE CONTAINED HEREIN ARE FURNISHED AS IS, WHERE IS, WITH ALL FAULTS AND WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING ANY WARRANTY OF MERCHANTABILITY, NON-INFRINGEMENT OR FITNESS FOR ANY PARTICULAR PURPOSE. Further, use of any of these smart contracts and code may be restricted or prohibited under applicable law, including securities laws, and it is therefore strongly advised for you to contact a reputable attorney in any jurisdiction where these smart contracts and code may be accessible for any questions or concerns with respect thereto. Further, no information provided in this repo should be construed as investment advice or legal advice for any particular facts or circumstances, and is not meant to replace competent counsel. a16z is not liable for any use of the foregoing, and users should proceed with caution and use at their own risk. See a16z.com/disclosures for more info._ 42 | -------------------------------------------------------------------------------- /examples/simple/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ["../../tests/lib", 'lib'] 5 | 6 | evm_version = 'cancun' 7 | 8 | force = false 9 | # compile options used by halmos (to prevent unnecessary recompilation when running forge test and halmos together) 10 | extra_output = ["storageLayout", "metadata"] 11 | -------------------------------------------------------------------------------- /examples/simple/remappings.txt: -------------------------------------------------------------------------------- 1 | multicaller/=src/multicaller/ 2 | openzeppelin/=../../tests/lib/openzeppelin-contracts/contracts/ 3 | -------------------------------------------------------------------------------- /examples/simple/src/BadElections.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ECDSA} from "openzeppelin/utils/cryptography/ECDSA.sol"; 5 | 6 | /// DO NOT USE, this demonstrates signature malleability problems 7 | contract BadElections { 8 | event Voted(uint256 proposalId, bool support, address voter); 9 | 10 | mapping (bytes32 => bool) hasVoted; 11 | 12 | // maps proposalId to vote count 13 | mapping (uint256 => uint256) public votesFor; 14 | mapping (uint256 => uint256) public votesAgainst; 15 | 16 | // https://eips.ethereum.org/EIPS/eip-2098 17 | // available in older OZ versions: 18 | // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.0/contracts/utils/cryptography/ECDSA.sol#L57 19 | function recoverCompactSignature(bytes32 hash, bytes memory signature) internal pure returns (address) { 20 | if (signature.length == 64) { 21 | bytes32 r; 22 | bytes32 vs; 23 | // ecrecover takes the signature parameters, and the only way to get them 24 | // currently is to use assembly. 25 | /// @solidity memory-safe-assembly 26 | assembly { 27 | r := mload(add(signature, 0x20)) 28 | vs := mload(add(signature, 0x40)) 29 | } 30 | return ECDSA.recover(hash, r, vs); 31 | } else { 32 | return address(0); 33 | } 34 | } 35 | 36 | function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { 37 | if (signature.length == 64) { 38 | return recoverCompactSignature(hash, signature); 39 | } else { 40 | return ECDSA.recover(hash, signature); 41 | } 42 | } 43 | 44 | // vote on a proposal by signature, anyone can cast a vote on behalf of someone else 45 | function vote(uint256 proposalId, bool support, address voter, bytes calldata signature) public { 46 | bytes32 sigHash = keccak256(signature); 47 | require(!hasVoted[sigHash], "already voted"); 48 | 49 | bytes32 badSigDigest = keccak256(abi.encode(proposalId, support, voter)); 50 | address recovered = recover(badSigDigest, signature); 51 | require(recovered == voter, "invalid signature"); 52 | require(recovered != address(0), "invalid signature"); 53 | 54 | // prevent replay 55 | hasVoted[sigHash] = true; 56 | 57 | // record vote 58 | if (support) { 59 | votesFor[proposalId]++; 60 | } else { 61 | votesAgainst[proposalId]++; 62 | } 63 | 64 | emit Voted(proposalId, support, voter); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/simple/src/IsPowerOfTwo.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract IsPowerOfTwo { 5 | 6 | function isPowerOfTwo(uint x) public pure returns (bool) { 7 | unchecked { 8 | return x != 0 && (x & (x - 1)) == 0; 9 | } 10 | } 11 | 12 | function isPowerOfTwoIter(uint x) public pure returns (bool) { 13 | unchecked { 14 | while (x != 0 && (x & 1) == 0) x >>= 1; // NOTE: `--loop 256` option needed for complete verification 15 | return x == 1; 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /examples/simple/src/MiniVat.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity ^0.8.17; 3 | 4 | /// @notice from https://github.com/baolean/symexec-bench/blob/main/Vat/MiniVat.sol 5 | 6 | contract MiniVat { 7 | uint256 public debt; 8 | mapping(address => uint256) public art; 9 | mapping(address => uint256) public dai; 10 | uint256 public Art; 11 | uint256 public rate; 12 | 13 | function init() public { 14 | require(rate == 0, "rate not zero"); 15 | rate = 10 ** 27; 16 | } 17 | 18 | function move(address dst, int256 wad) public { 19 | address src = msg.sender; 20 | dai[src] = wad > 0 21 | ? dai[src] - uint256(wad) 22 | : dai[src] + uint256(-1 * wad); 23 | dai[dst] = wad > 0 24 | ? dai[dst] + uint256(wad) 25 | : dai[dst] - uint256(-1 * wad); 26 | } 27 | 28 | function frob(int256 dart) public { 29 | address usr = msg.sender; 30 | 31 | int256 _art = int256(art[usr]) + dart; 32 | require(_art >= 0, "negative art"); 33 | art[usr] = uint256(_art); 34 | 35 | require(rate <= uint256(type(int256).max), "rate exceeds max int256"); 36 | int256 ddai = dart * int256(rate); 37 | 38 | int256 _dai = int256(dai[usr]) + ddai; 39 | require(_dai >= 0, "negative dai"); 40 | dai[usr] = uint256(_dai); 41 | 42 | Art = dart > 0 ? Art + uint256(dart) : Art - uint256(-1 * dart); 43 | debt = ddai > 0 ? debt + uint256(ddai) : debt - uint256(-1 * ddai); 44 | } 45 | 46 | function fold(int256 delta) public { 47 | address usr = msg.sender; 48 | rate = delta > 0 ? rate + uint256(delta) : rate - uint256(-1 * delta); 49 | require(Art <= uint256(type(int256).max), "Art exceeds max int256"); 50 | int256 ddai = int256(Art) * delta; 51 | dai[usr] = ddai > 0 52 | ? dai[usr] + uint256(ddai) 53 | : dai[usr] - uint256(-1 * ddai); 54 | debt = ddai > 0 ? debt + uint256(ddai) : debt - uint256(-1 * ddai); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/simple/src/TotalPrice.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract TotalPrice { 5 | 6 | function totalPriceBuggy(uint96 price, uint32 quantity) public pure returns (uint128) { 7 | unchecked { 8 | return uint120(price) * quantity; 9 | } 10 | } 11 | 12 | function totalPriceFixed(uint96 price, uint32 quantity) public pure returns (uint128) { 13 | unchecked { 14 | return uint128(price) * quantity; 15 | } 16 | } 17 | 18 | function totalPriceConservative(uint96 price, uint32 quantity) public pure returns (uint128) { 19 | unchecked { 20 | return uint128(uint(price) * uint(quantity)); 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /examples/simple/src/Vault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract Vault { 5 | uint public totalAssets; 6 | uint public totalShares; 7 | 8 | function deposit(uint assets) public returns (uint shares) { 9 | shares = (assets * totalShares) / totalAssets; 10 | 11 | totalAssets += assets; 12 | totalShares += shares; 13 | } 14 | 15 | function mint(uint shares) public returns (uint assets) { 16 | assets = (shares * totalAssets) / totalShares; // buggy 17 | 18 | totalAssets += assets; 19 | totalShares += shares; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/simple/test/BadElections.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 7 | 8 | import {BadElections} from "src/BadElections.sol"; 9 | 10 | contract BadElectionsTest is SymTest, Test { 11 | BadElections elections; 12 | 13 | function setUp() public { 14 | elections = new BadElections(); 15 | } 16 | 17 | /// The output will look something like this: 18 | /// 19 | /// Running 1 tests for test/BadElections.t.sol:BadElectionsTest 20 | /// Counterexample: 21 | /// halmos_fakeSig_bytes_01 = 0x00000000000000000000000000000000000000000000000000000000000000003fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a100 (65 bytes) 22 | /// p_proposalId_uint256 = 0x0000000000000000000000000000000000000000000000000000000000000000 (0) 23 | /// [FAIL] check_canNotVoteTwice(uint256) (paths: 7, time: 0.63s, bounds: []) 24 | /// 25 | /// the counterexample values are not meaningful, but examining the trace shows 26 | /// that halmos found a signature s.t. the voter can vote twice on the same proposal, 27 | /// and the final vote count is 2 28 | function check_canNotVoteTwice(uint256 proposalId) public { 29 | // setup 30 | bool support = true; 31 | (address voter, uint256 privateKey) = makeAddrAndKey("voter"); 32 | 33 | bytes32 sigDigest = keccak256(abi.encode(proposalId, support, voter)); 34 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, sigDigest); 35 | bytes memory signature = abi.encodePacked(r, s, v); 36 | 37 | // we start with no vote 38 | assertEq(elections.votesFor(proposalId), 0); 39 | 40 | // when we cast the vote 41 | elections.vote(proposalId, support, voter, signature); 42 | 43 | // then the vote count increases 44 | assertEq(elections.votesFor(proposalId), 1); 45 | 46 | // when we vote again with the same signature, it reverts 47 | try elections.vote(proposalId, support, voter, signature) { 48 | assert(false); 49 | } catch { 50 | // expected 51 | } 52 | 53 | // when the same voter votes with a different signature 54 | elections.vote(proposalId, support, voter, svm.createBytes(65, "fakeSig")); 55 | 56 | // then the vote count remains unchanged 57 | // @note spoiler alert: it does not 58 | assertEq(elections.votesFor(proposalId), 1); 59 | } 60 | 61 | // shows that it is possible to derive a compact signature from a valid signature, 62 | // with no knowledge of the voter's private key 63 | // see https://eips.ethereum.org/EIPS/eip-2098 64 | function check_canFindCompactSignatureFromOriginal(uint256 proposalId, bool support, address voter) public { 65 | bytes memory originalSig = svm.createBytes(65, "originalSig"); 66 | bytes memory compactSig = svm.createBytes(64, "compactSig"); 67 | 68 | // given a valid vote 69 | elections.vote(proposalId, support, voter, originalSig); 70 | vm.assume(elections.votesFor(proposalId) == 1); 71 | 72 | // one should not be able to vote again with a compact signature 73 | elections.vote(proposalId, support, voter, compactSig); 74 | assertEq(elections.votesFor(proposalId), 1); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /examples/simple/test/Fork.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract Counter { 7 | uint public total; // slot 0 8 | 9 | mapping (address => uint) public map; // slot 1 10 | 11 | function increment(address user) public { 12 | map[user]++; 13 | total++; 14 | } 15 | } 16 | 17 | contract EmptyContract { } 18 | 19 | contract CounterForkTest is Test { 20 | Counter counter; 21 | 22 | // slot numbers found in "storageLayout" of Counter.json 23 | uint counter_total_slot = 0; 24 | uint counter_map_slot = 1; 25 | 26 | function setUp() public { 27 | // create a new (empty) contract 28 | counter = Counter(address(new EmptyContract())); 29 | 30 | // set the bytecode of `counter` to the given code 31 | vm.etch(address(counter), hex"608060405234801561000f575f80fd5b506004361061003f575f3560e01c80632ddbd13a1461004357806345f43dd81461005d578063b721ef6e14610072575b5f80fd5b61004b5f5481565b60405190815260200160405180910390f35b61007061006b3660046100cf565b610091565b005b61004b6100803660046100cf565b60016020525f908152604090205481565b6001600160a01b0381165f9081526001602052604081208054916100b4836100fc565b90915550505f805490806100c7836100fc565b919050555050565b5f602082840312156100df575f80fd5b81356001600160a01b03811681146100f5575f80fd5b9392505050565b5f6001820161011957634e487b7160e01b5f52601160045260245ffd5b506001019056fea26469706673582212202ef0183898a1560805c26d8e270f79f0c451b549a3d09da92d110096d1deffec64736f6c63430008150033"); 32 | 33 | // set the storage slots to the given values 34 | vm.store(address(counter), bytes32(counter_total_slot), bytes32(uint(12))); // counter.total = 12 35 | vm.store(address(counter), keccak256(abi.encode(address(0x1001), counter_map_slot)), bytes32(uint(7))); // counter.map[0x1001] = 7 36 | vm.store(address(counter), keccak256(abi.encode(address(0x1002), counter_map_slot)), bytes32(uint(5))); // counter.map[0x1002] = 5 37 | 38 | /* NOTE: do _not_ use the keccak256 hash images as the slot number, since keccak256 is interpreted differently during symbolic execution 39 | vm.store(address(counter), bytes32(0xf04c2c5f6f9b62a2b5225d778c263b65e9f9e981a3c2cee9583d90b6a62a361c), bytes32(uint(7))); // counter.map[0x1001] = 7 40 | vm.store(address(counter), bytes32(0x292339123265925891d3d1c06602cc560d8bb722fcb2db8d37c0fc7a3456fc09), bytes32(uint(5))); // counter.map[0x1002] = 5 41 | */ 42 | } 43 | 44 | function check_setup() public { 45 | assertEq(counter.total(), 12); 46 | assertEq(counter.map(address(0x1001)), 7); 47 | assertEq(counter.map(address(0x1002)), 5); 48 | assertEq(counter.map(address(0x1003)), 0); // uninitialized storage slots default to a zero value 49 | } 50 | 51 | function check_invariant(address user) public { 52 | assertLe(counter.map(user), counter.total()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/simple/test/IsPowerOfTwo.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "../src/IsPowerOfTwo.sol"; 5 | 6 | /// @custom:halmos --solver-timeout-assertion 0 7 | contract IsPowerOfTwoTest { 8 | IsPowerOfTwo target; 9 | 10 | function setUp() public { 11 | target = new IsPowerOfTwo(); 12 | } 13 | 14 | function check_isPowerOfTwo_small(uint8 x) public view { 15 | bool result1 = target.isPowerOfTwo(x); 16 | bool result2 = x == 1 || x == 2 || x == 4 || x == 8 || x == 16 || x == 32 || x == 64 || x == 128; 17 | assert(result1 == result2); 18 | } 19 | 20 | /// @custom:halmos --loop 256 21 | function check_isPowerOfTwo(uint256 x) public view { 22 | bool result1 = target.isPowerOfTwo(x); 23 | bool result2 = false; 24 | for (uint i = 0; i < 256; i++) { // NOTE: `--loop 256` option needed for complete verification 25 | if (x == 2**i) { 26 | result2 = true; 27 | break; 28 | } 29 | } 30 | assert(result1 == result2); 31 | } 32 | 33 | /// @custom:halmos --loop 256 34 | function check_eq_isPowerOfTwo_isPowerOfTwoIter(uint x) public view { 35 | bool result1 = target.isPowerOfTwo(x); 36 | bool result2 = target.isPowerOfTwoIter(x); 37 | assert(result1 == result2); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/simple/test/MiniVat.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity ^0.8.13; 3 | 4 | /// @notice from https://github.com/aviggiano/property-based-testing-benchmark/blob/main/projects/dai-certora/test/TargetFunctions.sol 5 | 6 | import "forge-std/Test.sol"; 7 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 8 | 9 | import {MiniVat} from "../src/MiniVat.sol"; 10 | 11 | // halmos --contract MiniVatTest --solver-command 'yices-smt2 --smt2-model-format' 12 | contract MiniVatTest is Test, SymTest { 13 | MiniVat public minivat; 14 | 15 | function setUp() public { 16 | minivat = new MiniVat(); 17 | } 18 | 19 | function invariant_dai() public { 20 | assertEq( 21 | minivat.debt(), 22 | minivat.Art() * minivat.rate(), 23 | "The Fundamental Equation of DAI" 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/simple/test/Reentrancy.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 6 | 7 | import {ERC1155, IERC1155} from "openzeppelin/token/ERC1155/ERC1155.sol"; 8 | import {ERC1155Holder} from "openzeppelin/token/ERC1155/utils/ERC1155Holder.sol"; 9 | import {ReentrancyGuard} from "openzeppelin/utils/ReentrancyGuard.sol"; 10 | 11 | uint256 constant TOKEN_ID = 1; 12 | 13 | contract ERC1155Mock is ERC1155 { 14 | constructor() ERC1155("URI") { 15 | _mint(msg.sender, TOKEN_ID, 1_000_000_000 ether, ""); 16 | } 17 | } 18 | 19 | contract ReentrancyVault is ReentrancyGuard, ERC1155Holder { 20 | mapping(address => uint) balances; 21 | IERC1155 token; 22 | 23 | constructor (IERC1155 _token) { 24 | token = _token; 25 | } 26 | 27 | function deposit(uint value) public payable { 28 | token.safeTransferFrom(msg.sender, address(this), TOKEN_ID, value, ""); 29 | balances[msg.sender] += value; 30 | } 31 | 32 | // ReentrancyTest will pass if nonReentrant is applied 33 | function withdraw() public /* nonReentrant */ { 34 | uint balance = balances[msg.sender]; 35 | // reentrancy vulnerability 36 | token.safeTransferFrom(address(this), msg.sender, TOKEN_ID, balance, ""); 37 | balances[msg.sender] = 0; 38 | } 39 | } 40 | 41 | contract Attacker is SymTest { 42 | uint depth; // reentrancy depth 43 | address target; // reentrancy target 44 | 45 | function setDepth(uint _depth) public { 46 | depth = _depth; 47 | } 48 | 49 | function setTarget(address _target) public { 50 | target = _target; 51 | } 52 | 53 | function onERC1155Received( 54 | address, /* operator */ 55 | address, /* from */ 56 | uint256, /* id */ 57 | uint256, /* value */ 58 | bytes calldata /* data */ 59 | ) external returns (bytes4) { 60 | if (depth == 0) return this.onERC1155Received.selector; 61 | depth--; 62 | 63 | bytes memory data = svm.createCalldata(target); 64 | target.call(data); 65 | 66 | return this.onERC1155Received.selector; 67 | } 68 | } 69 | 70 | /// @custom:halmos --early-exit 71 | contract ReentrancyTest is SymTest, Test, ERC1155Holder { 72 | ERC1155Mock token; 73 | ReentrancyVault vault; 74 | Attacker attacker; 75 | 76 | uint256 constant ATTACKER_INITIAL_BALANCE = 100_000_000 ether; 77 | 78 | function setUp() public { 79 | token = new ERC1155Mock(); 80 | 81 | // deploy vault with initial deposit 82 | vault = new ReentrancyVault(IERC1155(token)); 83 | token.setApprovalForAll(address(vault), true); 84 | vault.deposit(1_000_000 ether); 85 | 86 | // deploy attacker with initial balance 87 | attacker = new Attacker(); 88 | token.safeTransferFrom(address(this), address(attacker), TOKEN_ID, ATTACKER_INITIAL_BALANCE, ""); 89 | 90 | // make attacker's initial deposit into vault 91 | vm.startPrank(address(attacker)); 92 | token.setApprovalForAll(address(vault), true); 93 | vault.deposit(1_000_000 ether); 94 | vm.stopPrank(); 95 | 96 | // configure attacker 97 | attacker.setDepth(1); 98 | attacker.setTarget(address(vault)); 99 | 100 | targetContract(address(vault)); 101 | } 102 | 103 | function check_setup() public { 104 | assertEq(token.balanceOf(address(this), TOKEN_ID), 999_000_000 ether - ATTACKER_INITIAL_BALANCE); 105 | assertEq(token.balanceOf(address(attacker), TOKEN_ID), 99_000_000 ether); 106 | assertEq(token.balanceOf(address(vault), TOKEN_ID), 2_000_000 ether); 107 | } 108 | 109 | function invariant_no_stolen_funds() public { 110 | assertLe(token.balanceOf(address(attacker), TOKEN_ID), ATTACKER_INITIAL_BALANCE); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /examples/simple/test/SimpleState.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 6 | 7 | // 8 | // Example from ItyFuzz paper (Figure 2): https://arxiv.org/pdf/2306.17135 9 | // 10 | 11 | contract SimpleState { 12 | uint counter = 0; 13 | 14 | function incr(uint x) public { 15 | require(x <= counter); 16 | counter += 1; 17 | } 18 | 19 | function decr(uint x) public { 20 | require(x >= counter); 21 | counter -= 1; 22 | } 23 | 24 | function buggy() public view returns (bool) { 25 | return counter == 10; 26 | } 27 | } 28 | 29 | contract SimpleStateTest is SymTest, Test { 30 | SimpleState target; 31 | 32 | function setUp() public { 33 | target = new SimpleState(); 34 | } 35 | 36 | /// @custom:halmos --invariant-depth 10 37 | function invariant_buggy() public { 38 | assertFalse(target.buggy()); 39 | } 40 | 41 | function check_buggy_excluding_view() public { 42 | bool success; 43 | 44 | // note: a total of 253 feasible paths are generated, of which only 10 unique states exist 45 | for (uint i = 0; i < 10; i++) { 46 | (success,) = address(target).call(svm.createCalldata("SimpleState")); // excluding view functions 47 | vm.assume(success); 48 | } 49 | 50 | assertFalse(target.buggy()); 51 | } 52 | 53 | function check_buggy_with_storage_snapshot() public { 54 | bool success; 55 | 56 | // take the initial storage snapshot 57 | uint prev = svm.snapshotStorage(address(target)); 58 | 59 | // note: a total of 253 feasible paths are generated, of which only 10 unique states exist 60 | for (uint i = 0; i < 10; i++) { 61 | (success,) = address(target).call(svm.createCalldata("SimpleState", true)); // including view functions 62 | vm.assume(success); 63 | 64 | // ignore if no storage changes 65 | uint curr = svm.snapshotStorage(address(target)); 66 | vm.assume(curr != prev); 67 | prev = curr; 68 | } 69 | 70 | assertFalse(target.buggy()); 71 | } 72 | 73 | function check_buggy_with_state_snapshot() public { 74 | bool success; 75 | 76 | // take the initial state snapshot 77 | uint prev = vm.snapshotState(); 78 | 79 | // note: a total of 253 feasible paths are generated, of which only 10 unique states exist 80 | for (uint i = 0; i < 10; i++) { 81 | (success,) = address(target).call(svm.createCalldata("SimpleState", true)); // including view functions 82 | vm.assume(success); 83 | 84 | // ignore if no state changes 85 | uint curr = vm.snapshotState(); 86 | vm.assume(curr != prev); 87 | prev = curr; 88 | } 89 | 90 | assertFalse(target.buggy()); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /examples/simple/test/TotalPrice.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "../src/TotalPrice.sol"; 5 | 6 | /// @custom:halmos --solver-timeout-assertion 0 7 | contract TotalPriceTest { 8 | TotalPrice target; 9 | 10 | function setUp() public { 11 | target = new TotalPrice(); 12 | } 13 | 14 | function check_totalPriceBuggy(uint96 price, uint32 quantity) public view { 15 | uint128 total = target.totalPriceBuggy(price, quantity); 16 | assert(quantity == 0 || total >= price); 17 | } 18 | 19 | function check_totalPriceFixed(uint96 price, uint32 quantity) public view { 20 | uint128 total = target.totalPriceFixed(price, quantity); 21 | assert(quantity == 0 || total >= price); 22 | } 23 | 24 | function check_eq_totalPriceFixed_totalPriceConservative(uint96 price, uint32 quantity) public view { 25 | uint128 total1 = target.totalPriceFixed(price, quantity); 26 | uint128 total2 = target.totalPriceConservative(price, quantity); 27 | assert(total1 == total2); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/simple/test/Vat.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Test.sol"; 5 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 6 | 7 | import {Vat} from "../src/Vat.sol"; 8 | 9 | // halmos --contract VatTest --solver-command 'yices-smt2 --smt2-model-format' 10 | /// @custom:halmos --early-exit 11 | contract VatTest is Test, SymTest { 12 | Vat public vat; 13 | bytes32 ilk; 14 | 15 | function setUp() public { 16 | vat = new Vat(); 17 | ilk = "gems"; 18 | 19 | vat.init(ilk); 20 | } 21 | 22 | function invariant_dai() public { 23 | assertEq( 24 | vat.debt(), 25 | vat.vice() + vat.Art(ilk) * vat.rate(ilk), 26 | "The Fundamental Equation of DAI" 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/simple/test/Vault.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 5 | 6 | import {Vault} from "../src/Vault.sol"; 7 | 8 | contract VaultMock is Vault { 9 | function setTotalAssets(uint _totalAssets) public { 10 | totalAssets = _totalAssets; 11 | } 12 | 13 | function setTotalShares(uint _totalShares) public { 14 | totalShares = _totalShares; 15 | } 16 | } 17 | 18 | /// @custom:halmos --solver-timeout-assertion 0 19 | contract VaultTest is SymTest { 20 | VaultMock vault; 21 | 22 | function setUp() public { 23 | vault = new VaultMock(); 24 | 25 | vault.setTotalAssets(svm.createUint256("A1")); 26 | vault.setTotalShares(svm.createUint256("S1")); 27 | } 28 | 29 | /// need to set a timeout for this test, the solver can run for hours 30 | /// @custom:halmos --solver-timeout-assertion 10000 31 | function check_deposit(uint assets) public { 32 | uint A1 = vault.totalAssets(); 33 | uint S1 = vault.totalShares(); 34 | 35 | vault.deposit(assets); 36 | 37 | uint A2 = vault.totalAssets(); 38 | uint S2 = vault.totalShares(); 39 | 40 | // assert(A1 / S1 <= A2 / S2); 41 | assert(A1 * S2 <= A2 * S1); // no counterexample 42 | } 43 | 44 | function check_mint(uint shares) public { 45 | uint A1 = vault.totalAssets(); 46 | uint S1 = vault.totalShares(); 47 | 48 | vault.mint(shares); 49 | 50 | uint A2 = vault.totalAssets(); 51 | uint S2 = vault.totalShares(); 52 | 53 | // assert(A1 / S1 <= A2 / S2); 54 | assert(A1 * S2 <= A2 * S1); // counterexamples exist 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/tokens/ERC20/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["../../../tests/lib", "lib"] 5 | 6 | evm_version = 'cancun' 7 | -------------------------------------------------------------------------------- /examples/tokens/ERC20/remappings.txt: -------------------------------------------------------------------------------- 1 | openzeppelin/=../../../tests/lib/openzeppelin-contracts/contracts/ 2 | ds-test/=../../../tests/lib/forge-std/lib/ds-test/src/ 3 | -------------------------------------------------------------------------------- /examples/tokens/ERC20/src/BackdoorERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol"; 5 | 6 | contract BackdoorERC20 is ERC20 { 7 | constructor(string memory name, string memory symbol, uint256 initialSupply, address deployer) ERC20(name, symbol) { 8 | _mint(deployer, initialSupply); 9 | } 10 | 11 | function backdoorTransferFrom(address from, address to, uint256 value) public returns (bool) { 12 | /* no allowance check 13 | address spender = _msgSender(); 14 | _spendAllowance(from, spender, value); 15 | */ 16 | _transfer(from, to, value); 17 | return true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/tokens/ERC20/src/OpenZeppelinERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol"; 5 | 6 | contract OpenZeppelinERC20 is ERC20 { 7 | constructor(string memory name, string memory symbol, uint256 initialSupply, address deployer) ERC20(name, symbol) { 8 | _mint(deployer, initialSupply); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/tokens/ERC20/src/SoladyERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC20} from "solady/tokens/ERC20.sol"; 5 | 6 | contract SoladyERC20 is ERC20 { 7 | string internal _name; 8 | string internal _symbol; 9 | uint8 internal _decimals; 10 | 11 | constructor( 12 | string memory name_, 13 | string memory symbol_, 14 | uint8 decimals_, 15 | uint256 initialSupply, 16 | address deployer 17 | ) { 18 | _name = name_; 19 | _symbol = symbol_; 20 | _decimals = decimals_; 21 | 22 | _mint(deployer, initialSupply); 23 | } 24 | 25 | function name() public view virtual override returns (string memory) { 26 | return _name; 27 | } 28 | 29 | function symbol() public view virtual override returns (string memory) { 30 | return _symbol; 31 | } 32 | 33 | function decimals() public view virtual override returns (uint8) { 34 | return _decimals; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/tokens/ERC20/src/SolmateERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC20} from "solmate/tokens/ERC20.sol"; 5 | 6 | contract SolmateERC20 is ERC20 { 7 | constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 initialSupply, address deployer) ERC20(_name, _symbol, _decimals) { 8 | _mint(deployer, initialSupply); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/tokens/ERC20/test/BackdoorERC20.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC20Test} from "./ERC20Test.sol"; 5 | 6 | import {BackdoorERC20} from "../src/BackdoorERC20.sol"; 7 | 8 | import {IERC20} from "forge-std/interfaces/IERC20.sol"; 9 | 10 | // empty interface 11 | interface IEmpty { } 12 | 13 | /// @custom:halmos --solver-timeout-assertion 0 14 | contract BackdoorERC20Test is ERC20Test { 15 | 16 | /// @custom:halmos --solver-timeout-branching 1000 17 | function setUp() public override { 18 | address deployer = address(0x1000); 19 | 20 | BackdoorERC20 token_ = new BackdoorERC20("BackdoorERC20", "BackdoorERC20", 1_000_000_000e18, deployer); 21 | token = address(token_); 22 | 23 | holders = new address[](3); 24 | holders[0] = address(0x1001); 25 | holders[1] = address(0x1002); 26 | holders[2] = address(0x1003); 27 | 28 | for (uint i = 0; i < holders.length; i++) { 29 | address account = holders[i]; 30 | uint256 balance = svm.createUint256('balance'); 31 | vm.prank(deployer); 32 | token_.transfer(account, balance); 33 | for (uint j = 0; j < i; j++) { 34 | address other = holders[j]; 35 | uint256 amount = svm.createUint256('amount'); 36 | vm.prank(account); 37 | token_.approve(other, amount); 38 | } 39 | } 40 | } 41 | 42 | function check_NoBackdoor_with_createBytes(bytes4 selector, address caller, address other) public { 43 | // arbitrary bytes as calldata 44 | bytes memory args = svm.createBytes(1024, 'data'); 45 | bytes memory data = abi.encodePacked(selector, args); 46 | _checkNoBackdoor(data, caller, other); // backdoor counterexample 47 | } 48 | 49 | function check_NoBackdoor_with_createCalldata_BackdoorERC20(bytes4 selector, address caller, address other) public { 50 | // calldata created using explicit BackdoorERC20 abi 51 | bytes memory data = svm.createCalldata("BackdoorERC20"); 52 | vm.assume(selector == bytes4(data)); // to enhance counterexample 53 | _checkNoBackdoor(data, caller, other); // backdoor counterexample 54 | } 55 | 56 | // NOTE: a backdoor counterexample can be found even if the abi information used to generate calldata doesn't include the backdoor function. 57 | // This is because the createCalldata cheatcode also generates fallback calldata which can match any other functions in the target contract. 58 | // 59 | // Caveat: the fallback calldata is essentially the same as the arbitrary bytes generated by the createBytes() cheatcode. 60 | // This means that if the functions matched by fallback calldata have dynamic-sized parameters, symbolic calldata offset errors may occur. 61 | // The main advantage of using createCalldata() is its more reliable handling of dynamic-sized parameters, which helps to avoid such symbolic offset errors. 62 | 63 | function check_NoBackdoor_with_createCalldata_IERC20(bytes4 selector, address caller, address other) public { 64 | // calldata created using the standard ERC20 interface abi 65 | bytes memory data = svm.createCalldata("IERC20"); 66 | vm.assume(selector == bytes4(data)); // to enhance counterexample 67 | _checkNoBackdoor(data, caller, other); // backdoor counterexample 68 | } 69 | 70 | function check_NoBackdoor_with_createCalldata_IEmpty(bytes4 selector, address caller, address other) public { 71 | // calldata created using an empty interface 72 | bytes memory data = svm.createCalldata("IEmpty"); 73 | vm.assume(selector == bytes4(data)); // to enhance counterexample 74 | _checkNoBackdoor(data, caller, other); // backdoor counterexample 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /examples/tokens/ERC20/test/ERC20Test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 5 | import {Test} from "forge-std/Test.sol"; 6 | 7 | import {IERC20} from "forge-std/interfaces/IERC20.sol"; 8 | 9 | abstract contract ERC20Test is SymTest, Test { 10 | // erc20 token address 11 | address internal token; 12 | 13 | // token holders 14 | address[] internal holders; 15 | 16 | function setUp() public virtual; 17 | 18 | function _checkNoBackdoor(bytes4 selector, bytes memory args, address caller, address other) public virtual { 19 | _checkNoBackdoor(abi.encodePacked(selector, args), caller, other); 20 | } 21 | 22 | function _checkNoBackdoor(bytes memory data, address caller, address other) public virtual { 23 | // consider two arbitrary distinct accounts 24 | vm.assume(other != caller); 25 | 26 | // record their current balances 27 | uint256 oldBalanceOther = IERC20(token).balanceOf(other); 28 | 29 | uint256 oldAllowance = IERC20(token).allowance(other, caller); 30 | 31 | // consider an arbitrary function call to the token from the caller 32 | vm.prank(caller); 33 | (bool success,) = address(token).call(data); 34 | vm.assume(success); 35 | 36 | uint256 newBalanceOther = IERC20(token).balanceOf(other); 37 | 38 | // ensure that the caller cannot spend other' tokens without approvals 39 | if (newBalanceOther < oldBalanceOther) { 40 | assert(oldAllowance >= oldBalanceOther - newBalanceOther); 41 | } 42 | } 43 | 44 | function check_transfer(address sender, address receiver, address other, uint256 amount) public virtual { 45 | // consider other that are neither sender or receiver 46 | require(other != sender); 47 | require(other != receiver); 48 | 49 | // record their current balance 50 | uint256 oldBalanceSender = IERC20(token).balanceOf(sender); 51 | uint256 oldBalanceReceiver = IERC20(token).balanceOf(receiver); 52 | uint256 oldBalanceOther = IERC20(token).balanceOf(other); 53 | 54 | vm.prank(sender); 55 | IERC20(token).transfer(receiver, amount); 56 | 57 | if (sender != receiver) { 58 | assert(IERC20(token).balanceOf(sender) <= oldBalanceSender); // ensure no subtraction overflow 59 | assert(IERC20(token).balanceOf(sender) == oldBalanceSender - amount); 60 | assert(IERC20(token).balanceOf(receiver) >= oldBalanceReceiver); // ensure no addition overflow 61 | assert(IERC20(token).balanceOf(receiver) == oldBalanceReceiver + amount); 62 | } else { 63 | // sender and receiver may be the same 64 | assert(IERC20(token).balanceOf(sender) == oldBalanceSender); 65 | assert(IERC20(token).balanceOf(receiver) == oldBalanceReceiver); 66 | } 67 | // make sure other balance is not affected 68 | assert(IERC20(token).balanceOf(other) == oldBalanceOther); 69 | } 70 | 71 | function check_transferFrom(address caller, address from, address to, address other, uint256 amount) public virtual { 72 | require(other != from); 73 | require(other != to); 74 | 75 | uint256 oldBalanceFrom = IERC20(token).balanceOf(from); 76 | uint256 oldBalanceTo = IERC20(token).balanceOf(to); 77 | uint256 oldBalanceOther = IERC20(token).balanceOf(other); 78 | 79 | uint256 oldAllowance = IERC20(token).allowance(from, caller); 80 | 81 | vm.prank(caller); 82 | IERC20(token).transferFrom(from, to, amount); 83 | 84 | if (from != to) { 85 | assert(IERC20(token).balanceOf(from) <= oldBalanceFrom); 86 | assert(IERC20(token).balanceOf(from) == oldBalanceFrom - amount); 87 | assert(IERC20(token).balanceOf(to) >= oldBalanceTo); 88 | assert(IERC20(token).balanceOf(to) == oldBalanceTo + amount); 89 | 90 | assert(oldAllowance >= amount); // ensure allowance was enough 91 | assert(oldAllowance == type(uint256).max || IERC20(token).allowance(from, caller) == oldAllowance - amount); // allowance decreases if not max 92 | } else { 93 | assert(IERC20(token).balanceOf(from) == oldBalanceFrom); 94 | assert(IERC20(token).balanceOf(to) == oldBalanceTo); 95 | } 96 | assert(IERC20(token).balanceOf(other) == oldBalanceOther); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /examples/tokens/ERC20/test/OpenZeppelinERC20.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC20Test} from "./ERC20Test.sol"; 5 | 6 | import {OpenZeppelinERC20} from "../src/OpenZeppelinERC20.sol"; 7 | 8 | /// @custom:halmos --solver-timeout-assertion 0 9 | contract OpenZeppelinERC20Test is ERC20Test { 10 | 11 | /// @custom:halmos --solver-timeout-branching 1000 12 | function setUp() public override { 13 | address deployer = address(0x1000); 14 | 15 | OpenZeppelinERC20 token_ = new OpenZeppelinERC20("OpenZeppelinERC20", "OpenZeppelinERC20", 1_000_000_000e18, deployer); 16 | token = address(token_); 17 | 18 | holders = new address[](3); 19 | holders[0] = address(0x1001); 20 | holders[1] = address(0x1002); 21 | holders[2] = address(0x1003); 22 | 23 | for (uint i = 0; i < holders.length; i++) { 24 | address account = holders[i]; 25 | uint256 balance = svm.createUint256('balance'); 26 | vm.prank(deployer); 27 | token_.transfer(account, balance); 28 | for (uint j = 0; j < i; j++) { 29 | address other = holders[j]; 30 | uint256 amount = svm.createUint256('amount'); 31 | vm.prank(account); 32 | token_.approve(other, amount); 33 | } 34 | } 35 | } 36 | 37 | function check_NoBackdoor(bytes4 selector, address caller, address other) public { 38 | bytes memory args = svm.createBytes(1024, 'data'); 39 | _checkNoBackdoor(selector, args, caller, other); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/tokens/ERC20/test/SoladyERC20.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC20Test} from "./ERC20Test.sol"; 5 | 6 | import {SoladyERC20} from "../src/SoladyERC20.sol"; 7 | 8 | /// @custom:halmos --storage-layout=generic --solver-timeout-assertion 0 9 | contract SoladyERC20Test is ERC20Test { 10 | 11 | /// @custom:halmos --solver-timeout-branching 1000 12 | function setUp() public override { 13 | address deployer = address(0x1000); 14 | 15 | SoladyERC20 token_ = new SoladyERC20("SoladyERC20", "SoladyERC20", 18, 1_000_000_000e18, deployer); 16 | token = address(token_); 17 | 18 | holders = new address[](3); 19 | holders[0] = address(0x1001); 20 | holders[1] = address(0x1002); 21 | holders[2] = address(0x1003); 22 | 23 | for (uint i = 0; i < holders.length; i++) { 24 | address account = holders[i]; 25 | uint256 balance = svm.createUint256('balance'); 26 | vm.prank(deployer); 27 | token_.transfer(account, balance); 28 | for (uint j = 0; j < i; j++) { 29 | address other = holders[j]; 30 | uint256 amount = svm.createUint256('amount'); 31 | vm.prank(account); 32 | token_.approve(other, amount); 33 | } 34 | } 35 | } 36 | 37 | function check_NoBackdoor(bytes4 selector, address caller, address other) public { 38 | bytes memory args = svm.createBytes(1024, 'data'); 39 | _checkNoBackdoor(selector, args, caller, other); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/tokens/ERC20/test/SolmateERC20.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC20Test} from "./ERC20Test.sol"; 5 | 6 | import {SolmateERC20} from "../src/SolmateERC20.sol"; 7 | 8 | /// @custom:halmos --solver-timeout-assertion 0 9 | contract SolmateERC20Test is ERC20Test { 10 | 11 | /// @custom:halmos --solver-timeout-branching 1000 12 | function setUp() public override { 13 | address deployer = address(0x1000); 14 | 15 | SolmateERC20 token_ = new SolmateERC20("SolmateERC20", "SolmateERC20", 18, 1_000_000_000e18, deployer); 16 | token = address(token_); 17 | 18 | holders = new address[](3); 19 | holders[0] = address(0x1001); 20 | holders[1] = address(0x1002); 21 | holders[2] = address(0x1003); 22 | 23 | for (uint i = 0; i < holders.length; i++) { 24 | address account = holders[i]; 25 | uint256 balance = svm.createUint256('balance'); 26 | vm.prank(deployer); 27 | token_.transfer(account, balance); 28 | for (uint j = 0; j < i; j++) { 29 | address other = holders[j]; 30 | uint256 amount = svm.createUint256('amount'); 31 | vm.prank(account); 32 | token_.approve(other, amount); 33 | } 34 | } 35 | } 36 | 37 | function check_NoBackdoor(bytes4 selector, address caller, address other) public { 38 | bytes memory args = svm.createBytes(1024, 'data'); 39 | _checkNoBackdoor(selector, args, caller, other); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/tokens/ERC721/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["../../../tests/lib", "lib"] 5 | 6 | evm_version = 'cancun' 7 | -------------------------------------------------------------------------------- /examples/tokens/ERC721/remappings.txt: -------------------------------------------------------------------------------- 1 | openzeppelin/=../../../tests/lib/openzeppelin-contracts/contracts/ 2 | ds-test/=../../../tests/lib/forge-std/lib/ds-test/src/ 3 | -------------------------------------------------------------------------------- /examples/tokens/ERC721/src/OpenZeppelinERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC721} from "openzeppelin/token/ERC721/ERC721.sol"; 5 | 6 | contract OpenZeppelinERC721 is ERC721 { 7 | constructor(string memory name, string memory symbol, uint256 initialSupply, address deployer) ERC721(name, symbol) { 8 | for (uint256 i = 1; i <= initialSupply; i++) { 9 | _mint(deployer, i); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/tokens/ERC721/src/SoladyERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC721} from "solady/tokens/ERC721.sol"; 5 | 6 | contract SoladyERC721 is ERC721 { 7 | string internal _name; 8 | string internal _symbol; 9 | 10 | constructor( 11 | string memory name_, 12 | string memory symbol_, 13 | uint256 initialSupply, 14 | address deployer 15 | ) { 16 | _name = name_; 17 | _symbol = symbol_; 18 | 19 | for (uint256 i = 1; i <= initialSupply; i++) { 20 | _mint(deployer, i); 21 | } 22 | } 23 | 24 | function name() public view virtual override returns (string memory) { 25 | return _name; 26 | } 27 | 28 | function symbol() public view virtual override returns (string memory) { 29 | return _symbol; 30 | } 31 | 32 | function tokenURI(uint256) public view virtual override returns (string memory) {} 33 | } 34 | -------------------------------------------------------------------------------- /examples/tokens/ERC721/src/SolmateERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC721} from "solmate/tokens/ERC721.sol"; 5 | 6 | contract SolmateERC721 is ERC721 { 7 | constructor(string memory name, string memory symbol, uint256 initialSupply, address deployer) ERC721(name, symbol) { 8 | for (uint256 i = 1; i <= initialSupply; i++) { 9 | _mint(deployer, i); 10 | } 11 | } 12 | 13 | function tokenURI(uint256) public pure virtual override returns (string memory) {} 14 | } 15 | -------------------------------------------------------------------------------- /examples/tokens/ERC721/test/OpenZeppelinERC721.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC721Test} from "./ERC721Test.sol"; 5 | 6 | import {OpenZeppelinERC721} from "../src/OpenZeppelinERC721.sol"; 7 | 8 | /// @custom:halmos --solver-timeout-assertion 0 9 | contract OpenZeppelinERC721Test is ERC721Test { 10 | 11 | /// @custom:halmos --solver-timeout-branching 1000 12 | function setUp() public override { 13 | deployer = address(0x1000); 14 | 15 | OpenZeppelinERC721 token_ = new OpenZeppelinERC721("OpenZeppelinERC721", "OpenZeppelinERC721", 5, deployer); 16 | token = address(token_); 17 | 18 | accounts = new address[](3); 19 | accounts[0] = address(0x1001); 20 | accounts[1] = address(0x1002); 21 | accounts[2] = address(0x1003); 22 | 23 | tokenIds = new uint256[](5); 24 | tokenIds[0] = 1; 25 | tokenIds[1] = 2; 26 | tokenIds[2] = 3; 27 | tokenIds[3] = 4; 28 | tokenIds[4] = 5; 29 | 30 | // account0: {token0, token1}, account1: {token2}, account2: {token3} 31 | vm.startPrank(deployer); 32 | token_.transferFrom(deployer, accounts[0], tokenIds[0]); 33 | token_.transferFrom(deployer, accounts[0], tokenIds[1]); 34 | token_.transferFrom(deployer, accounts[1], tokenIds[2]); 35 | token_.transferFrom(deployer, accounts[2], tokenIds[3]); 36 | vm.stopPrank(); 37 | 38 | vm.prank(accounts[0]); 39 | token_.approve(accounts[2], tokenIds[0]); 40 | 41 | vm.prank(accounts[1]); 42 | token_.setApprovalForAll(accounts[2], true); 43 | } 44 | 45 | function check_NoBackdoor() public { 46 | bytes memory _calldata = svm.createCalldata("OpenZeppelinERC721"); 47 | _check_NoBackdoor(_calldata); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/tokens/ERC721/test/SoladyERC721.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC721Test} from "./ERC721Test.sol"; 5 | 6 | import {SoladyERC721} from "../src/SoladyERC721.sol"; 7 | 8 | /// @custom:halmos --storage-layout=generic --solver-timeout-assertion 0 9 | contract SoladyERC721Test is ERC721Test { 10 | 11 | /// @custom:halmos --solver-timeout-branching 1000 12 | function setUp() public override { 13 | deployer = address(0x1000); 14 | 15 | SoladyERC721 token_ = new SoladyERC721("SoladyERC721", "SoladyERC721", 5, deployer); 16 | token = address(token_); 17 | 18 | accounts = new address[](3); 19 | accounts[0] = address(0x1001); 20 | accounts[1] = address(0x1002); 21 | accounts[2] = address(0x1003); 22 | 23 | tokenIds = new uint256[](5); 24 | tokenIds[0] = 1; 25 | tokenIds[1] = 2; 26 | tokenIds[2] = 3; 27 | tokenIds[3] = 4; 28 | tokenIds[4] = 5; 29 | 30 | // account0: {token0, token1}, account1: {token2}, account2: {token3} 31 | vm.prank(deployer); 32 | token_.transferFrom(deployer, accounts[0], tokenIds[0]); 33 | vm.prank(deployer); 34 | token_.transferFrom(deployer, accounts[0], tokenIds[1]); 35 | vm.prank(deployer); 36 | token_.transferFrom(deployer, accounts[1], tokenIds[2]); 37 | vm.prank(deployer); 38 | token_.transferFrom(deployer, accounts[2], tokenIds[3]); 39 | 40 | vm.prank(accounts[0]); 41 | token_.approve(accounts[2], tokenIds[0]); 42 | 43 | vm.prank(accounts[1]); 44 | token_.setApprovalForAll(accounts[2], true); 45 | } 46 | 47 | function check_NoBackdoor() public { 48 | bytes memory _calldata = svm.createCalldata("SoladyERC721"); 49 | _check_NoBackdoor(_calldata); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/tokens/ERC721/test/SolmateERC721.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {ERC721Test} from "./ERC721Test.sol"; 5 | 6 | import {SolmateERC721} from "../src/SolmateERC721.sol"; 7 | 8 | /// @custom:halmos --solver-timeout-assertion 0 9 | contract SolmateERC721Test is ERC721Test { 10 | 11 | /// @custom:halmos --solver-timeout-branching 1000 12 | function setUp() public override { 13 | deployer = address(0x1000); 14 | 15 | SolmateERC721 token_ = new SolmateERC721("SolmateERC721", "SolmateERC721", 5, deployer); 16 | token = address(token_); 17 | 18 | accounts = new address[](3); 19 | accounts[0] = address(0x1001); 20 | accounts[1] = address(0x1002); 21 | accounts[2] = address(0x1003); 22 | 23 | tokenIds = new uint256[](5); 24 | tokenIds[0] = 1; 25 | tokenIds[1] = 2; 26 | tokenIds[2] = 3; 27 | tokenIds[3] = 4; 28 | tokenIds[4] = 5; 29 | 30 | // account0: {token0, token1}, account1: {token2}, account2: {token3} 31 | vm.prank(deployer); 32 | token_.transferFrom(deployer, accounts[0], tokenIds[0]); 33 | vm.prank(deployer); 34 | token_.transferFrom(deployer, accounts[0], tokenIds[1]); 35 | vm.prank(deployer); 36 | token_.transferFrom(deployer, accounts[1], tokenIds[2]); 37 | vm.prank(deployer); 38 | token_.transferFrom(deployer, accounts[2], tokenIds[3]); 39 | 40 | vm.prank(accounts[0]); 41 | token_.approve(accounts[2], tokenIds[0]); 42 | 43 | vm.prank(accounts[1]); 44 | token_.setApprovalForAll(accounts[2], true); 45 | } 46 | 47 | function check_NoBackdoor() public { 48 | bytes memory _calldata = svm.createCalldata("SolmateERC721"); 49 | _check_NoBackdoor(_calldata); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/halmos-builder/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/a16z/solvers:latest 2 | 3 | RUN apt-get update && apt-get install --no-install-recommends -y \ 4 | git \ 5 | python3 \ 6 | python3-pip \ 7 | wget \ 8 | curl \ 9 | python3-venv \ 10 | build-essential \ 11 | clang \ 12 | python3.12-dev \ 13 | && rm -rf /var/lib/apt/lists/* 14 | 15 | # Create necessary directories for foundry 16 | RUN mkdir -p /root/.foundry/bin /root/.foundry/share/man/man1 17 | 18 | # Add foundry binaries to PATH 19 | ENV PATH="/root/.foundry/bin:${PATH}" 20 | 21 | # Download and run foundryup 22 | RUN curl -sSf -L https://raw.githubusercontent.com/foundry-rs/foundry/master/foundryup/foundryup \ 23 | -o /root/.foundry/bin/foundryup && \ 24 | chmod +x /root/.foundry/bin/foundryup && \ 25 | foundryup 26 | 27 | # Set a nicer prompt 28 | ENV IMAGE_NAME=halmos-builder 29 | RUN echo 'PS1="($IMAGE_NAME) \[\033[1;32m\]\u@\h \[\033[1;35m\]\w \$\[\033[0m\] "' >> /root/.bashrc 30 | -------------------------------------------------------------------------------- /packages/halmos/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/a16z/halmos-builder:latest 2 | 3 | # inspired by https://hynek.me/articles/docker-uv/ 4 | 5 | # - enable the virtual environment 6 | # - install halmos and its dependencies in UV_PROJECT_ENVIRONMENT 7 | # - enable bytecode compilation for faster startup 8 | # - disable downloading any additional packages 9 | # - use copy mode for linking instead of symlinking (because we mount to /src temporarily) 10 | ENV PATH="/halmos/bin:$PATH" \ 11 | VIRTUAL_ENV='/halmos' \ 12 | UV_PROJECT_ENVIRONMENT='/halmos' \ 13 | UV_COMPILE_BYTECODE=1 \ 14 | UV_PYTHON=python3.13 \ 15 | UV_PYTHON_DOWNLOADS=never \ 16 | UV_LINK_MODE=copy 17 | 18 | # Install halmos, assuming it is checked out in the current host directory 19 | # we don't specify --frozen or --locked because we don't check in uv.lock 20 | RUN --mount=type=bind,source=../..,target=/src,readonly=false \ 21 | cd /src && \ 22 | uv sync --extra dev --no-editable 23 | 24 | # Set a nicer prompt 25 | ENV IMAGE_NAME=halmos 26 | RUN echo 'PS1="($IMAGE_NAME) \[\033[1;32m\]\u@\h \[\033[1;35m\]\w \$\[\033[0m\] "' >> /root/.bashrc 27 | 28 | # optional: print python version, site packages, and check that halmos can be imported 29 | RUN <> /root/.bashrc 99 | 100 | # Set the default command for the container 101 | WORKDIR /workspace 102 | CMD ["/bin/bash"] 103 | -------------------------------------------------------------------------------- /packages/solvers/README.md: -------------------------------------------------------------------------------- 1 | # solvers package 2 | 3 | A minimalist Docker image containing high-performance SMT solvers. 4 | 5 | ## Quick start 6 | 7 | ```sh 8 | # Step 1: Pull the image 9 | docker pull ghcr.io/a16z/solvers:latest 10 | 11 | # Step 2: Tag the image with a shorter name 12 | docker tag ghcr.io/a16z/solvers:latest solvers 13 | 14 | # Step 3: Run the container using the shorter name, 15 | for solver in bitwuzla boolector cvc5 stp yices z3 ; do \ 16 | echo --- $solver && \ 17 | docker run --rm solvers $solver --version ; \ 18 | done 19 | 20 | # Step 4: create an example smt2 file: 21 | cat << EOF > checkSanity.smt2 22 | (set-logic QF_BV) 23 | (assert (= (bvsdiv (_ bv3 2) (_ bv2 2)) (_ bv0 2))) 24 | (check-sat) 25 | (exit) 26 | EOF 27 | 28 | # Step 5: invoke each solver on the file 29 | # (`-v .:/workspace` mounts the current working directory under /workspace on the container, making the files available there) 30 | for solver in bitwuzla boolector cvc5 stp yices-smt2 z3 ; do \ 31 | echo -n "$solver: " && \ 32 | docker run --rm -v .:/workspace solvers $solver checkSanity.smt2 ; \ 33 | done 34 | ``` 35 | 36 | ## Available solvers 37 | 38 | ``` 39 | bitwuzla: 0.6.0-dev-main@5b2d83f 40 | boolector: 3.2.4 41 | cvc5: 1.1.2 42 | stp: 2.3.3 43 | yices: 2.6.4 44 | z3: 4.13.3 45 | ``` 46 | 47 | ## Contributing 48 | 49 | Everyone is welcome to contribute new solvers or new versions to the image via pull requests. If a solver is competitive at [SMT-COMP](https://smt-comp.github.io), it would be great to have it included in the image. 50 | 51 | When possible, we prefer release binaries from an official source like the Github releases for the project to minimize the time it takes to build the image. If you're unsure about a particular solver or how to integrate it, consider reaching out on the [Halmos Dev Chat](https://t.me/+4UhzHduai3MzZmUx). 52 | 53 | Before opening the pull request, please test your changes by verifying: 54 | 55 | * that you can build the image locally 56 | * that you can correctly invoke the solver as described in the [Quick Start](https://github.com/a16z/halmos/tree/main/packages/solvers#quick-start) section 57 | * update this README with the name, version and source of the solver 58 | 59 | Thank you in advance! 60 | 61 | 62 | ## Credit 63 | 64 | Based on [EmperorOrokuSaki/solvers](https://github.com/EmperorOrokuSaki/solvers) 65 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0", "setuptools_scm[toml]>=6.2"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.setuptools_scm] 6 | 7 | [project] 8 | name = "halmos" 9 | description = "A symbolic testing tool for EVM smart contracts" 10 | readme = "README.md" 11 | authors = [ 12 | { name="a16z crypto" }, 13 | ] 14 | maintainers = [ 15 | { name="Daejun Park" }, 16 | { name="karmacoma " }, 17 | ] 18 | classifiers = [ 19 | "Programming Language :: Python :: 3", 20 | "License :: OSI Approved :: GNU Affero General Public License v3", 21 | "Operating System :: OS Independent", 22 | ] 23 | requires-python = ">=3.11" 24 | dependencies = [ 25 | "sortedcontainers>=2.4.0", 26 | "toml>=0.10.2", 27 | "z3-solver==4.12.6.0", 28 | "eth_hash[pysha3]>=0.7.0", 29 | "rich>=13.9.4", 30 | "xxhash>=3.5.0", 31 | "psutil>=6.1.0", 32 | "requests>=2.32.3", 33 | "yices-solver>=2.6.4,<2.6.5", 34 | ] 35 | dynamic = ["version"] 36 | 37 | [project.scripts] 38 | halmos = "halmos.__main__:main" 39 | 40 | [project.urls] 41 | "Homepage" = "https://github.com/a16z/halmos" 42 | 43 | # development dependencies, can be installed with 44 | # uv sync --extra dev 45 | # or 46 | # pip install -e ".[dev]" 47 | # (see CONTRIBUTING.md for more details) 48 | [project.optional-dependencies] 49 | dev = [ 50 | "pre-commit>=4.0.1", 51 | "pytest>=8.3.4", 52 | "ruff>=0.8.1", 53 | ] 54 | 55 | [tool.pytest.ini_options] 56 | # TODO: re-add test_traces.py when we have a better way to support it in CI 57 | addopts = "--ignore=tests/lib --ignore=tests/test_traces.py" 58 | # addopts = "--ignore=tests/lib" 59 | 60 | [tool.ruff.lint] 61 | select = [ 62 | "E", # pycodestyle 63 | "F", # pyflakes 64 | "UP", # pyupgrade 65 | "B", # flake8-bugbear 66 | "SIM",# flake8-simplify 67 | "I", # isort 68 | ] 69 | ignore = [ 70 | "E501", # line too long 71 | ] 72 | exclude = [ 73 | "tests/lib/**" 74 | ] 75 | -------------------------------------------------------------------------------- /requirements-benchmark.txt: -------------------------------------------------------------------------------- 1 | # optional dependencies, for benchmarking 2 | pytest-benchmark 3 | pytest-benchmark[histogram] 4 | -------------------------------------------------------------------------------- /src/halmos/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: AGPL-3.0 2 | -------------------------------------------------------------------------------- /src/halmos/console.py: -------------------------------------------------------------------------------- 1 | from z3 import BitVec 2 | 3 | from halmos.bitvec import HalmosBitVec as BV 4 | from halmos.logs import ( 5 | info, 6 | warn, 7 | ) 8 | from halmos.utils import ( 9 | extract_bytes, 10 | extract_bytes_argument, 11 | extract_funsig, 12 | extract_string_argument, 13 | hexify, 14 | int_of, 15 | magenta, 16 | render_address, 17 | render_bool, 18 | render_bytes, 19 | render_int, 20 | render_uint, 21 | ) 22 | 23 | 24 | def log_uint256(arg: BitVec) -> None: 25 | b = extract_bytes(arg, 4, 32) 26 | console.log(render_uint(b)) 27 | 28 | 29 | def log_string(arg: BitVec) -> None: 30 | str_val = extract_string_argument(arg, 0) 31 | console.log(str_val) 32 | 33 | 34 | def log_bytes(arg: BitVec) -> None: 35 | b = extract_bytes_argument(arg, 0) 36 | console.log(render_bytes(b)) 37 | 38 | 39 | def log_string_address(arg: BitVec) -> None: 40 | str_val = extract_string_argument(arg, 0) 41 | addr = extract_bytes(arg, 36, 32) 42 | console.log(f"{str_val} {render_address(addr)}") 43 | 44 | 45 | def log_address(arg: BitVec) -> None: 46 | addr = extract_bytes(arg, 4, 32) 47 | console.log(render_address(addr)) 48 | 49 | 50 | def log_string_bool(arg: BitVec) -> None: 51 | str_val = extract_string_argument(arg, 0) 52 | bool_val = extract_bytes(arg, 36, 32) 53 | console.log(f"{str_val} {render_bool(bool_val)}") 54 | 55 | 56 | def log_bool(arg: BitVec) -> None: 57 | bool_val = extract_bytes(arg, 4, 32) 58 | console.log(render_bool(bool_val)) 59 | 60 | 61 | def log_string_string(arg: BitVec) -> None: 62 | str1_val = extract_string_argument(arg, 0) 63 | str2_val = extract_string_argument(arg, 1) 64 | console.log(f"{str1_val} {str2_val}") 65 | 66 | 67 | def log_bytes32(arg: BitVec) -> None: 68 | b = extract_bytes(arg, 4, 32) 69 | console.log(hexify(b)) 70 | 71 | 72 | def log_string_int256(arg: BitVec) -> None: 73 | str_val = extract_string_argument(arg, 0) 74 | int_val = extract_bytes(arg, 36, 32) 75 | console.log(f"{str_val} {render_int(int_val)}") 76 | 77 | 78 | def log_int256(arg: BitVec) -> None: 79 | int_val = extract_bytes(arg, 4, 32) 80 | console.log(render_int(int_val)) 81 | 82 | 83 | def log_string_uint256(arg: BitVec) -> None: 84 | str_val = extract_string_argument(arg, 0) 85 | uint_val = extract_bytes(arg, 36, 32) 86 | console.log(f"{str_val} {render_uint(uint_val)}") 87 | 88 | 89 | class console: 90 | # see forge-std/console2.sol 91 | address = BV(0x000000000000000000636F6E736F6C652E6C6F67, size=160) 92 | 93 | handlers = { 94 | 0xF82C50F1: log_uint256, 95 | 0xF5B1BBA9: log_uint256, # alias for 'log(uint)' 96 | 0x41304FAC: log_string, 97 | 0x0BE77F56: log_bytes, 98 | 0x319AF333: log_string_address, 99 | 0x2C2ECBC2: log_address, 100 | 0xC3B55635: log_string_bool, 101 | 0x32458EED: log_bool, 102 | 0x4B5C4277: log_string_string, 103 | 0x27B7CF85: log_bytes32, 104 | 0x3CA6268E: log_string_int256, 105 | 0x2D5B6CB9: log_int256, 106 | 0xB60E72CC: log_string_uint256, 107 | } 108 | 109 | @staticmethod 110 | def log(what: str) -> None: 111 | print(f"[console.log] {magenta(what)}") 112 | 113 | @staticmethod 114 | def handle(ex, arg: BitVec) -> None: 115 | try: 116 | funsig: int = int_of( 117 | extract_funsig(arg), "symbolic console function selector" 118 | ) 119 | 120 | if handler := console.handlers.get(funsig): 121 | return handler(arg) 122 | 123 | info( 124 | f"Unsupported console function: selector = 0x{funsig:0>8x}, " 125 | f"calldata = {hexify(arg)}" 126 | ) 127 | except Exception as e: 128 | # we don't want to fail execution because of an issue during console.log 129 | warn(f"console.handle: {repr(e)} with arg={hexify(arg)}") 130 | -------------------------------------------------------------------------------- /src/halmos/constants.py: -------------------------------------------------------------------------------- 1 | VERBOSITY_TRACE_COUNTEREXAMPLE = 2 2 | VERBOSITY_TRACE_SETUP = 3 3 | VERBOSITY_TRACE_PATHS = 4 4 | VERBOSITY_TRACE_CONSTRUCTOR = 5 5 | 6 | MAX_MEMORY_SIZE = 2**20 7 | 8 | # to be compatible with startHoax(address), 9 | # see https://github.com/a16z/halmos/issues/338 10 | MAX_ETH = 1 << 128 11 | -------------------------------------------------------------------------------- /src/halmos/logs.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from dataclasses import dataclass 3 | 4 | from rich.logging import RichHandler 5 | 6 | # 7 | # Basic logging 8 | # 9 | 10 | logging.basicConfig( 11 | format="%(message)s", 12 | handlers=[RichHandler(level=logging.NOTSET, show_time=False, show_path=False)], 13 | ) 14 | 15 | logger = logging.getLogger("halmos") 16 | 17 | 18 | # 19 | # Logging with filtering out duplicate log messages 20 | # 21 | 22 | 23 | class UniqueLoggingFilter(logging.Filter): 24 | def __init__(self): 25 | self.records = set() 26 | 27 | def filter(self, record): 28 | if record.msg in self.records: 29 | return False 30 | self.records.add(record.msg) 31 | return True 32 | 33 | 34 | logger_unique = logging.getLogger("halmos.unique") 35 | logger_unique.addFilter(UniqueLoggingFilter()) 36 | 37 | 38 | def logger_for(allow_duplicate=True) -> logging.Logger: 39 | return logger if allow_duplicate else logger_unique 40 | 41 | 42 | def debug(text: str, allow_duplicate=True) -> None: 43 | logger_for(allow_duplicate).debug(text) 44 | 45 | 46 | def info(text: str, allow_duplicate=True) -> None: 47 | logger_for(allow_duplicate).info(text) 48 | 49 | 50 | def warn(text: str, allow_duplicate=True) -> None: 51 | logger_for(allow_duplicate).warning(text) 52 | 53 | 54 | def error(text: str, allow_duplicate=True) -> None: 55 | logger_for(allow_duplicate).error(text) 56 | 57 | 58 | def debug_once(text: str) -> None: 59 | debug(text, allow_duplicate=False) 60 | 61 | 62 | # 63 | # Warnings with error code 64 | # 65 | 66 | WARNINGS_BASE_URL = "https://github.com/a16z/halmos/wiki/warnings" 67 | 68 | 69 | @dataclass 70 | class ErrorCode: 71 | code: str 72 | 73 | def url(self) -> str: 74 | return f"{WARNINGS_BASE_URL}#{self.code}" 75 | 76 | 77 | PARSING_ERROR = ErrorCode("parsing-error") 78 | INTERNAL_ERROR = ErrorCode("internal-error") 79 | LIBRARY_PLACEHOLDER = ErrorCode("library-placeholder") 80 | COUNTEREXAMPLE_INVALID = ErrorCode("counterexample-invalid") 81 | COUNTEREXAMPLE_UNKNOWN = ErrorCode("counterexample-unknown") 82 | UNSUPPORTED_OPCODE = ErrorCode("unsupported-opcode") 83 | REVERT_ALL = ErrorCode("revert-all") 84 | LOOP_BOUND = ErrorCode("loop-bound") 85 | 86 | 87 | def warn_code(error_code: ErrorCode, msg: str, allow_duplicate=True): 88 | logger_for(allow_duplicate).warning(f"{msg}\n(see {error_code.url()})") 89 | -------------------------------------------------------------------------------- /src/halmos/ui.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | from rich import get_console 4 | from rich.console import Console 5 | from rich.prompt import Confirm 6 | from rich.status import Status 7 | 8 | 9 | class suspend_status: 10 | """Context manager to temporarily suspend a Status.""" 11 | 12 | def __init__(self, status: Status): 13 | self.status = status 14 | 15 | def __enter__(self): 16 | self.status.stop() 17 | 18 | def __exit__(self, exc_type, exc_value, traceback): 19 | self.status.start() 20 | 21 | 22 | @dataclass(frozen=True, eq=False, order=False, slots=True) 23 | class UI: 24 | status: Status 25 | console: Console = field(default_factory=get_console) 26 | 27 | @property 28 | def is_interactive(self) -> bool: 29 | return self.console.is_interactive 30 | 31 | def clear_live(self): 32 | self.console.clear_live() 33 | 34 | def start_status(self): 35 | # clear any remaining live display before starting a new instance 36 | self.clear_live() 37 | self.status.start() 38 | 39 | def update_status(self, status: str): 40 | self.status.update(status) 41 | 42 | def stop_status(self): 43 | self.status.stop() 44 | 45 | def prompt(self, prompt: str) -> bool: 46 | # non-interactive sessions (e.g. redirected output) will block on input 47 | if not self.is_interactive: 48 | return False 49 | 50 | with suspend_status(self.status): 51 | return Confirm.ask(prompt) 52 | 53 | def print(self, *args, **kwargs): 54 | self.console.print(*args, **kwargs) 55 | 56 | 57 | ui: UI = UI(Status("")) 58 | 59 | 60 | if __name__ == "__main__": 61 | import time 62 | 63 | print(f"{ui.is_interactive=}") 64 | 65 | # things basically break down if start a rogue status (not managed by the UI class) 66 | # time.sleep(1) 67 | # print("starting a rogue status") 68 | # rogue_status = Status("Rogue status") 69 | # rogue_status.start() 70 | 71 | # time.sleep(1) 72 | # print("clearing live") 73 | # ui.clear_live() 74 | 75 | time.sleep(1) 76 | print("starting 'official' status") 77 | ui.start_status() 78 | 79 | time.sleep(1) 80 | print("updating status") 81 | ui.update_status("Status update demo") 82 | 83 | # using the managed prompt should suspend the status 84 | # (otherwise the prompt will get clobbered by the next status update) 85 | time.sleep(1) 86 | answer = ui.prompt("Prompt demo") 87 | print(f"{answer=}") 88 | 89 | # status updates should resume after the prompt returns 90 | time.sleep(1) 91 | print("stopping status") 92 | ui.stop_status() 93 | 94 | time.sleep(1) 95 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # workaround for ruff removing fixture imports 2 | # they look unused because of dependency injection by pytest 3 | from test_fixtures import * # noqa 4 | 5 | 6 | def pytest_addoption(parser): 7 | parser.addoption( 8 | "--halmos-options", 9 | metavar="OPTIONS", 10 | default="", 11 | help="Halmos commandline options", 12 | ) 13 | -------------------------------------------------------------------------------- /tests/expected/erc721.json: -------------------------------------------------------------------------------- 1 | { 2 | "exitcode": 0, 3 | "test_results": { 4 | "test/OpenZeppelinERC721.t.sol:OpenZeppelinERC721Test": [ 5 | { 6 | "name": "check_NoBackdoor()", 7 | "exitcode": 0, 8 | "num_models": 0, 9 | "models": null, 10 | "num_paths": null, 11 | "time": null, 12 | "num_bounded_loops": null 13 | }, 14 | { 15 | "name": "check_transferFrom(address,address,address,address,uint256,uint256)", 16 | "exitcode": 0, 17 | "num_models": 0, 18 | "models": null, 19 | "num_paths": null, 20 | "time": null, 21 | "num_bounded_loops": null 22 | } 23 | ], 24 | "test/SoladyERC721.t.sol:SoladyERC721Test": [ 25 | { 26 | "name": "check_NoBackdoor()", 27 | "exitcode": 0, 28 | "num_models": 0, 29 | "models": null, 30 | "num_paths": null, 31 | "time": null, 32 | "num_bounded_loops": null 33 | }, 34 | { 35 | "name": "check_transferFrom(address,address,address,address,uint256,uint256)", 36 | "exitcode": 0, 37 | "num_models": 0, 38 | "models": null, 39 | "num_paths": null, 40 | "time": null, 41 | "num_bounded_loops": null 42 | } 43 | ], 44 | "test/SolmateERC721.t.sol:SolmateERC721Test": [ 45 | { 46 | "name": "check_NoBackdoor()", 47 | "exitcode": 0, 48 | "num_models": 0, 49 | "models": null, 50 | "num_paths": null, 51 | "time": null, 52 | "num_bounded_loops": null 53 | }, 54 | { 55 | "name": "check_transferFrom(address,address,address,address,uint256,uint256)", 56 | "exitcode": 0, 57 | "num_models": 0, 58 | "models": null, 59 | "num_paths": null, 60 | "time": null, 61 | "num_bounded_loops": null 62 | } 63 | ] 64 | } 65 | } -------------------------------------------------------------------------------- /tests/expected/ffi.json: -------------------------------------------------------------------------------- 1 | { 2 | "exitcode": 1, 3 | "test_results": { 4 | "test/Ffi.t.sol:FfiTest": [ 5 | { 6 | "name": "check_Failure()", 7 | "exitcode": 5, 8 | "num_models": null, 9 | "models": null, 10 | "num_paths": null, 11 | "time": null, 12 | "num_bounded_loops": null 13 | }, 14 | { 15 | "name": "check_HexOutput()", 16 | "exitcode": 0, 17 | "num_models": 0, 18 | "models": null, 19 | "num_paths": null, 20 | "time": null, 21 | "num_bounded_loops": null 22 | }, 23 | { 24 | "name": "check_ImplicitHexStringOutput()", 25 | "exitcode": 0, 26 | "num_models": 0, 27 | "models": null, 28 | "num_paths": null, 29 | "time": null, 30 | "num_bounded_loops": null 31 | }, 32 | { 33 | "name": "check_Stderr()", 34 | "exitcode": 0, 35 | "num_models": 0, 36 | "models": null, 37 | "num_paths": null, 38 | "time": null, 39 | "num_bounded_loops": null 40 | }, 41 | { 42 | "name": "check_StringOutput()", 43 | "exitcode": 0, 44 | "num_models": 0, 45 | "models": null, 46 | "num_paths": null, 47 | "time": null, 48 | "num_bounded_loops": null 49 | } 50 | ] 51 | } 52 | } -------------------------------------------------------------------------------- /tests/expected/solver.json: -------------------------------------------------------------------------------- 1 | { 2 | "exitcode": 1, 3 | "test_results": { 4 | "test/Math.t.sol:MathTest": [ 5 | { 6 | "name": "check_Avg(uint256,uint256)", 7 | "exitcode": 1, 8 | "num_models": 1, 9 | "models": null, 10 | "num_paths": null, 11 | "time": null, 12 | "num_bounded_loops": null 13 | }, 14 | { 15 | "name": "check_deposit(uint256,uint256,uint256)", 16 | "exitcode": 2, 17 | "num_models": 0, 18 | "models": null, 19 | "num_paths": null, 20 | "time": null, 21 | "num_bounded_loops": null 22 | }, 23 | { 24 | "name": "check_mint(uint256,uint256,uint256)", 25 | "exitcode": 1, 26 | "num_models": 1, 27 | "models": null, 28 | "num_paths": null, 29 | "time": null, 30 | "num_bounded_loops": null 31 | } 32 | ], 33 | "test/SignedDiv.t.sol:TestBadWadMul": [ 34 | { 35 | "name": "check_wadMul_solEquivalent(int256,int256)", 36 | "exitcode": 1, 37 | "num_models": 1, 38 | "models": null, 39 | "num_paths": null, 40 | "time": null, 41 | "num_bounded_loops": null 42 | } 43 | ], 44 | "test/SignedDiv.t.sol:TestGoodWadMul": [ 45 | { 46 | "name": "check_wadMul_solEquivalent(int256,int256)", 47 | "exitcode": 0, 48 | "num_models": 0, 49 | "models": null, 50 | "num_paths": null, 51 | "time": null, 52 | "num_bounded_loops": null 53 | } 54 | ], 55 | "test/Solver.t.sol:SolverTest": [ 56 | { 57 | "name": "check_foo(uint256,uint256)", 58 | "exitcode": 1, 59 | "num_models": 1, 60 | "models": null, 61 | "num_paths": null, 62 | "time": null, 63 | "num_bounded_loops": null 64 | } 65 | ] 66 | } 67 | } -------------------------------------------------------------------------------- /tests/ffi/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['../lib', 'lib'] 5 | 6 | evm_version = 'cancun' 7 | 8 | force = false 9 | # compile options used by halmos (to prevent unnecessary recompilation when running forge test and halmos together) 10 | extra_output = ["storageLayout", "metadata"] 11 | -------------------------------------------------------------------------------- /tests/ffi/remappings.txt: -------------------------------------------------------------------------------- 1 | openzeppelin/=../lib/openzeppelin-contracts/contracts/ 2 | ds-test/=../lib/forge-std/lib/ds-test/src/ 3 | -------------------------------------------------------------------------------- /tests/ffi/test/Ffi.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract FfiTest is Test { 7 | 8 | function check_HexOutput() public { 9 | string[] memory inputs = new string[](3); 10 | inputs[0] = "echo"; 11 | inputs[1] = "-n"; 12 | inputs[2] = /* "arbitrary string" abi.encoded hex representation */"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001061726269747261727920737472696e6700000000000000000000000000000000"; 13 | 14 | bytes memory res = vm.ffi(inputs); 15 | 16 | bytes32 expected = keccak256(abi.encodePacked("arbitrary string")); 17 | bytes32 output = keccak256(abi.encodePacked(abi.decode(res, (string)))); 18 | 19 | assert(expected == output); 20 | } 21 | 22 | function check_ImplicitHexStringOutput() public { 23 | string[] memory inputs = new string[](3); 24 | inputs[0] = "echo"; 25 | inputs[1] = "-n"; 26 | inputs[2] = " 4243 "; 27 | 28 | bytes memory res = vm.ffi(inputs); 29 | assertEq(res.length, 2); 30 | assert(res[0] == 0x42); 31 | assert(res[1] == 0x43); 32 | } 33 | 34 | function check_StringOutput() public { 35 | string memory str = "arbitrary string"; 36 | 37 | string[] memory inputs = new string[](3); 38 | inputs[0] = "echo"; 39 | inputs[1] = "-n"; 40 | inputs[2] = str; 41 | 42 | bytes32 expected = keccak256(abi.encodePacked(str)); 43 | bytes32 output = keccak256( 44 | vm.ffi(inputs) /* Perform ffi */ 45 | ); 46 | 47 | assert(expected == output); 48 | } 49 | 50 | function check_Stderr() public { 51 | string[] memory inputs = new string[](3); 52 | inputs[0] = "logger"; 53 | inputs[1] = "-s"; 54 | inputs[2] = "Error!"; 55 | 56 | bytes32 output = keccak256( 57 | vm.ffi(inputs) /* Perform ffi that generates non empty stderr */ 58 | ); 59 | 60 | /* TODO: fix bug in sha3 of empty bytes 61 | bytes32 expected = keccak256(abi.encodePacked("")); 62 | assert(expected == output); 63 | */ 64 | } 65 | 66 | function check_Failure() public { 67 | string[] memory inputs = new string[](1); 68 | inputs[0] = "must_fail"; 69 | 70 | bytes32 output = keccak256( 71 | vm.ffi(inputs) /* Perform ffi that must fail */ 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/regression/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['../lib', 'lib'] 5 | 6 | evm_version = 'cancun' 7 | 8 | optimizer = true 9 | 10 | force = false 11 | 12 | # compile options used by halmos (to prevent unnecessary recompilation when running forge test and halmos together) 13 | extra_output = ["storageLayout", "metadata"] 14 | -------------------------------------------------------------------------------- /tests/regression/remappings.txt: -------------------------------------------------------------------------------- 1 | openzeppelin/=../lib/openzeppelin-contracts/contracts/ 2 | ds-test/=../lib/forge-std/lib/ds-test/src/ 3 | forge-std/=../lib/forge-std/src/ 4 | -------------------------------------------------------------------------------- /tests/regression/src/Const.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract Const { 5 | uint public constant const = 11; 6 | } 7 | -------------------------------------------------------------------------------- /tests/regression/src/Counter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract Counter { 5 | uint public cnt; 6 | 7 | constructor() {} 8 | 9 | function set(uint n) public { 10 | cnt = n; 11 | } 12 | 13 | function inc() public { 14 | cnt++; 15 | } 16 | 17 | function incOpt() public { 18 | unchecked { 19 | cnt++; 20 | } 21 | } 22 | 23 | function incBy(uint n) public { 24 | unchecked { 25 | cnt += n; 26 | } 27 | } 28 | 29 | function loopFor(uint n) public { 30 | for (uint i; i < n; i++) { 31 | cnt++; 32 | } 33 | } 34 | 35 | function loopWhile(uint n) public { 36 | uint i = 0; 37 | while (i < n) { 38 | cnt++; 39 | i++; 40 | } 41 | } 42 | 43 | function loopDoWhile(uint n) public { 44 | uint i = 0; 45 | do { 46 | cnt++; 47 | } while (++i < n); 48 | } 49 | 50 | function loopConst() public { 51 | for (uint i; i < 2; i++) { // default: `--loop 2` 52 | cnt++; 53 | } 54 | } 55 | 56 | mapping (uint => bool) map; 57 | function loopConstIf() public { 58 | // total # of paths is 16 (= 2^4), but only 6 (= 4C2) of them will be considered if `--loop 2` is given 59 | for (uint i; i < 4; i++) { 60 | if (map[i]) cnt++; 61 | } 62 | } 63 | 64 | function setSum(uint[2] memory arr) public { 65 | cnt = arr[0] + arr[1]; 66 | } 67 | 68 | function setString(string memory s) public { 69 | cnt += bytes(s).length; 70 | } 71 | 72 | function foo(uint a, uint b, uint c, uint d) public { 73 | bar(a); 74 | bar(b); 75 | bar(c); 76 | bar(d); 77 | } 78 | 79 | function bar(uint x) public { 80 | if (x > 10) cnt++; 81 | else cnt++; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/regression/src/Create.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract Create { 5 | uint public value; 6 | uint public immutable halmos; 7 | uint public initialized = 7; 8 | uint public constant const = 11; 9 | 10 | constructor(uint qed) { 11 | halmos = qed; 12 | } 13 | 14 | function set(uint x) public { 15 | value = x; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/regression/src/List.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract List { 5 | uint[] public arr; 6 | 7 | constructor() { } 8 | 9 | function size() public view returns (uint) { 10 | return arr.length; 11 | } 12 | 13 | function add(uint x) public { 14 | arr.push(x); 15 | } 16 | 17 | function remove() public { 18 | arr.pop(); 19 | } 20 | 21 | function set(uint i, uint x) public { 22 | arr[i] = x; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/regression/src/SignExtend.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract SignExtend { 5 | function changeMySign(int16 x) public pure returns (int16) { 6 | return -x; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/regression/src/Storage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract Storage { 5 | uint public num; 6 | 7 | mapping (uint => uint) public map1; 8 | mapping (uint => mapping (uint => uint)) public map2; 9 | mapping (uint => mapping (uint => mapping (uint => uint))) public map3; 10 | 11 | uint[] public arr1; 12 | uint[][] public arr2; 13 | 14 | mapping (uint => uint[]) public map1Arr1; 15 | 16 | constructor () { } 17 | 18 | function setMap1(uint k, uint v) public { 19 | map1[k] = v; 20 | } 21 | 22 | function setMap2(uint k1, uint k2, uint v) public { 23 | map2[k1][k2] = v; 24 | } 25 | 26 | function setMap3(uint k1, uint k2, uint k3, uint v) public { 27 | map3[k1][k2][k3] = v; 28 | } 29 | 30 | function addArr1(uint v) public { 31 | arr1.push(v); 32 | } 33 | 34 | function addArr2(uint i, uint v) public { 35 | arr2[i].push(v); 36 | } 37 | 38 | function addMap1Arr1(uint k, uint v) public { 39 | map1Arr1[k].push(v); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/regression/test/Arith.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract ArithTest { 5 | function unchecked_div(uint x, uint y) public pure returns (uint ret) { 6 | assembly { 7 | ret := div(x, y) 8 | } 9 | } 10 | 11 | function unchecked_mod(uint x, uint y) public pure returns (uint ret) { 12 | assembly { 13 | ret := mod(x, y) 14 | } 15 | } 16 | 17 | function check_Mod(uint x, uint y, address addr) public pure { 18 | unchecked { 19 | assert(unchecked_mod(x, 0) == 0); // compiler rejects `x % 0` 20 | assert(x % 1 == 0); 21 | assert(x % 2 < 2); 22 | assert(x % 4 < 4); 23 | 24 | uint x_mod_y = unchecked_mod(x, y); 25 | // assert(x_mod_y == 0 || x_mod_y < y); // not supported // TODO: support more axioms 26 | assert(x_mod_y <= y); 27 | 28 | assert(uint256(uint160(addr)) % (2**160) == uint256(uint160(addr))); 29 | } 30 | } 31 | 32 | function check_Exp(uint x) public pure { 33 | unchecked { 34 | assert(x ** 0 == 1); // 0 ** 0 == 1 35 | assert(x ** 1 == x); 36 | assert(x ** 2 == x * x); 37 | assert((x ** 2) ** 2 == x * x * x * x); 38 | assert(((x ** 2) ** 2) ** 2 == (x**2) * (x**2) * (x**2) * (x**2)); 39 | // assert(x ** 8 == (x ** 4) ** 2); 40 | } 41 | } 42 | 43 | function check_Div_fail(uint x, uint y) public pure { 44 | require(x > y); 45 | 46 | uint q = unchecked_div(x, y); 47 | 48 | // note: since x > y, q can be zero only when y == 0, due to the division-by-zero semantics in the EVM 49 | 50 | assert(q != 0); // counterexample: y == 0 51 | } 52 | 53 | function check_Div_pass(uint x, uint y) public pure { 54 | require(x > y); 55 | require(y > 0); 56 | 57 | uint q = unchecked_div(x, y); 58 | 59 | assert(q != 0); // pass 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/regression/test/AssertTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract C is Test { 7 | function foo() public pure { 8 | assert(false); // not propagated 9 | } 10 | 11 | function bar() public { 12 | fail(); // propagated 13 | } 14 | } 15 | 16 | contract AssertTest is Test { 17 | C c; 18 | 19 | function setUp() public { 20 | c = new C(); 21 | } 22 | 23 | function check_assert_not_propagated() public { 24 | address(c).call(abi.encodeWithSelector(C.foo.selector, bytes(""))); // pass 25 | } 26 | 27 | function check_fail_propagated() public { 28 | address(c).call(abi.encodeWithSelector(C.bar.selector, bytes(""))); // fail 29 | } 30 | 31 | function check_symbolic_revert(uint256 x) public { 32 | // reverts with Concat(0x4e487b71, p_x_uint256()) 33 | // halmos only considers reverts with explicit revert codes so we expect a PASS here 34 | // this is really to make sure we handle symbolic reverts gracefully 35 | if (x > 0) { 36 | bytes memory data = abi.encodeWithSignature("Panic(uint256)", x); 37 | assembly { 38 | revert(add(data, 0x20), mload(data)) 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/regression/test/Block.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract BlockCheatCodeTest is Test { 7 | function check_fee(uint x) public { 8 | assertEq(block.basefee, 0); // foundry default value 9 | vm.fee(x); 10 | assert(block.basefee == x); 11 | } 12 | 13 | function check_chainId(uint64 x) public { 14 | assertEq(block.chainid, 31337); // foundry default value 15 | vm.chainId(x); 16 | assert(block.chainid == x); 17 | } 18 | 19 | function check_coinbase(address x) public { 20 | assertEq(block.coinbase, address(0)); // foundry default value 21 | vm.coinbase(x); 22 | assert(block.coinbase == x); 23 | } 24 | 25 | function check_difficulty(uint x) public { 26 | assertEq(block.difficulty, 0); // foundry default value 27 | vm.difficulty(x); 28 | assert(block.difficulty == x); 29 | } 30 | 31 | function check_gaslimit() public { 32 | assertEq(block.gaslimit, 2**63 - 1); // foundry default value 33 | } 34 | 35 | function check_roll(uint x) public { 36 | assertEq(block.number, 1); // foundry default value 37 | vm.roll(x); 38 | assert(block.number == x); 39 | } 40 | 41 | function check_warp(uint x) public { 42 | assertEq(block.timestamp, 1); // foundry default value 43 | vm.warp(x); 44 | assert(block.timestamp == x); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/regression/test/BlockNumber.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract BlockNumberTest is Test { 7 | uint256 blockNumber; 8 | 9 | function setUp() public { 10 | blockNumber = block.number; 11 | } 12 | 13 | function check_GetBlockNumber() public { 14 | uint256 vmBlockNumber = vm.getBlockNumber(); 15 | 16 | assert(vmBlockNumber == blockNumber); 17 | } 18 | 19 | function check_GetBlockNumber_AfterAdvance() public { 20 | // Advance the block number by 1000 blocks 21 | vm.roll(blockNumber + 1000); 22 | uint256 newBlockNumber = vm.getBlockNumber(); 23 | 24 | assert(newBlockNumber == blockNumber + 1000); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/regression/test/Buffers.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | import "forge-std/Test.sol"; 4 | 5 | contract BuffersTest is Test { 6 | function check_calldatacopy_large_offset() public { 7 | uint256 index = 1 ether; 8 | uint256 value; 9 | assembly { 10 | calldatacopy(0, index, 32) 11 | value := mload(0) 12 | } 13 | 14 | assertEq(value, 0); 15 | } 16 | 17 | function check_calldataload_large_offset() public { 18 | uint256 index = 1 ether; 19 | uint256 value; 20 | assembly { 21 | value := calldataload(index) 22 | } 23 | 24 | assertEq(value, 0); 25 | } 26 | 27 | function check_codecopy_large_offset() public { 28 | uint256 index = 1 ether; 29 | uint256 value; 30 | assembly { 31 | codecopy(0, index, 32) 32 | value := mload(0) 33 | } 34 | 35 | assertEq(value, 0); 36 | } 37 | 38 | function check_codecopy_offset_across_boundary() public { 39 | uint256 index = address(this).code.length - 16; 40 | uint256 value; 41 | assembly { 42 | codecopy(0, index, 32) 43 | value := mload(0) 44 | } 45 | 46 | assertNotEq(value, 0); 47 | } 48 | 49 | function check_extcodecopy_boundary() public { 50 | address target = address(this); 51 | uint256 index = target.code.length - 16; 52 | uint256 value; 53 | assembly { 54 | extcodecopy(target, 0, index, 32) 55 | value := mload(0) 56 | } 57 | 58 | assertNotEq(value, 0); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/regression/test/Byte.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract ByteTest { 5 | function byte1(uint i, uint x) public pure returns (uint r) { 6 | assembly { r := byte(i, x) } 7 | } 8 | 9 | function byte2(uint i, uint x) public pure returns (uint) { 10 | if (i >= 32) return 0; 11 | return (x >> (248-i*8)) & 0xff; 12 | } 13 | 14 | function byte3(uint i, uint x) public pure returns (uint) { 15 | if (i >= 32) return 0; 16 | bytes memory b = new bytes(32); 17 | assembly { mstore(add(b, 32), x) } 18 | return uint(uint8(bytes1(b[i]))); // TODO: Not supported: MLOAD symbolic memory offset: 160 + p_i_uint256 19 | } 20 | 21 | function check_byte(uint i, uint x) pure public { 22 | uint r1 = byte1(i, x); 23 | uint r2 = byte2(i, x); 24 | // uint r3 = byte3(i, x); // not supported 25 | assert(r1 == r2); 26 | // assert(r1 == r3); 27 | } 28 | } 29 | 30 | contract SymbolicByteTest { 31 | function check_SymbolicByteIndex(uint8 x, uint8 i) public pure returns (uint r) { 32 | if (x > 10) assert(false); // expected to fail 33 | assembly { 34 | r := byte(i, x) 35 | } 36 | assert(r == 0); // expected to fail with counterexample 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/regression/test/CallAlias.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract C { 7 | uint public num; 8 | 9 | function set(uint x) public { 10 | num = x; 11 | } 12 | } 13 | 14 | contract CallAliasTest is Test { 15 | C c1; 16 | C c2; 17 | 18 | function setUp() public { 19 | c1 = new C(); 20 | c2 = new C(); 21 | } 22 | 23 | function check_alias_1(address addr, uint x) public { 24 | if (addr == address(c1)) { 25 | C(addr).set(x); 26 | assert(c1.num() == x); 27 | assert(c2.num() == 0); 28 | } else if (addr == address(c2)) { 29 | C(addr).set(x); 30 | assert(c1.num() == 0); 31 | assert(c2.num() == x); 32 | } 33 | } 34 | 35 | function check_alias_2(address addr, uint x) public { 36 | if (addr == address(c1)) { 37 | assert(addr.codehash == address(c1).codehash); 38 | assert(addr.code.length == address(c1).code.length); 39 | assert(addr.code.length > 0); 40 | } else if (addr == address(this)) { 41 | assert(addr.codehash == address(this).codehash); 42 | assert(addr.code.length == address(this).code.length); 43 | assert(addr.code.length > 0); 44 | } 45 | } 46 | 47 | function check_alias_3(address addr, uint x) public { 48 | if (addr == address(c1)) { 49 | vm.store(addr, bytes32(0), bytes32(x)); 50 | assert(c1.num() == x); 51 | assert(c2.num() == 0); 52 | assert(uint(vm.load(addr, bytes32(0))) == x); 53 | } else if (addr == address(c2)) { 54 | vm.store(addr, bytes32(0), bytes32(x)); 55 | assert(c1.num() == 0); 56 | assert(c2.num() == x); 57 | assert(uint(vm.load(addr, bytes32(0))) == x); 58 | } 59 | } 60 | 61 | function check_alias_1a(bool mode, address addr, uint x) public { 62 | if (mode) { 63 | vm.assume(addr == address(c1)); 64 | } else { 65 | vm.assume(addr == address(c2)); 66 | } 67 | 68 | C(addr).set(x); 69 | 70 | if (mode) { 71 | assert(c1.num() == x); 72 | assert(c2.num() == 0); 73 | } else { 74 | assert(c1.num() == 0); 75 | assert(c2.num() == x); 76 | } 77 | } 78 | 79 | function check_alias_2a(bool mode, address addr, uint x) public { 80 | if (mode) { 81 | vm.assume(addr == address(c1)); 82 | } else { 83 | vm.assume(addr == address(this)); 84 | } 85 | 86 | if (mode) { 87 | assert(addr.codehash == address(c1).codehash); 88 | assert(addr.code.length == address(c1).code.length); 89 | } else { 90 | assert(addr.codehash == address(this).codehash); 91 | assert(addr.code.length == address(this).code.length); 92 | } 93 | 94 | assert(addr.code.length > 0); 95 | } 96 | 97 | function check_alias_3a(bool mode, address addr, uint x) public { 98 | if (mode) { 99 | vm.assume(addr == address(c1)); 100 | } else { 101 | vm.assume(addr == address(c2)); 102 | } 103 | 104 | vm.store(addr, bytes32(0), bytes32(x)); 105 | 106 | if (mode) { 107 | assert(c1.num() == x); 108 | assert(c2.num() == 0); 109 | } else { 110 | assert(c1.num() == 0); 111 | assert(c2.num() == x); 112 | } 113 | 114 | assert(uint(vm.load(addr, bytes32(0))) == x); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /tests/regression/test/Concretization.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 6 | 7 | contract C1 { 8 | function foo(bytes calldata data) external { 9 | uint offset; 10 | uint size; 11 | // This simulates custom calldata decoding: 12 | // 1. Copy static portion of calldata into memory 13 | // 2. Read offset and size from the copied memory 14 | // 3. Copy the actual data using calldatacopy 15 | assembly { 16 | // Copy the first 68 bytes of calldata into memory 17 | // This includes: 18 | // - 4 bytes for function selector 19 | // - 32 bytes for offset 20 | // - 32 bytes for size 21 | calldatacopy(0, 0, 68) 22 | 23 | // Read offset and size from copied memory 24 | // Note: Proper concretization is crucial here. 25 | // Without it, reading the size would fail due to a symbolic memory offset. 26 | offset := mload(4) 27 | size := mload(add(4, offset)) 28 | 29 | // Copy the actual data portion 30 | calldatacopy(68, 68, size) 31 | 32 | // Return the decoded data 33 | return(4, add(size, 64)) 34 | } 35 | } 36 | } 37 | 38 | contract C2 { 39 | function foo(bytes calldata data) external returns (bytes memory) { 40 | uint offset; 41 | uint size; 42 | // This simulates standard calldata decoding: 43 | // 1. Read offset and size directly from calldata using calldataload 44 | // 2. Copy the entire calldata into memory 45 | // 3. Copy the actual data portion 46 | assembly { 47 | // Read offset and size directly from calldata 48 | offset := calldataload(4) 49 | size := calldataload(add(4, offset)) 50 | 51 | // Copy the first 68 bytes of calldata into memory 52 | calldatacopy(0, 0, 68) 53 | 54 | // Copy the actual data portion 55 | calldatacopy(68, 68, size) 56 | 57 | // Return the decoded data 58 | return(4, add(size, 64)) 59 | } 60 | } 61 | } 62 | 63 | contract ConcretizationTest is SymTest, Test { 64 | address c1; 65 | address c2; 66 | 67 | function setUp() public { 68 | c1 = address(new C1()); 69 | c2 = address(new C2()); 70 | } 71 | 72 | function check_custom_calldata_decoding() public { 73 | bytes memory data = svm.createCalldata("Concretization.t.sol", "C1"); 74 | (bool success1, bytes memory retdata1) = c1.call(data); 75 | (bool success2, bytes memory retdata2) = c2.call(data); 76 | assertEq(success1, success2); 77 | assertEq(retdata1, retdata2); 78 | } 79 | 80 | function check_memory_index(uint idx) public { 81 | uint[3] memory arr = [uint(0), 1, 2]; 82 | 83 | vm.assume(idx == 0 || idx == 1 || idx == 2); 84 | 85 | // there are three paths at this point 86 | assertEq(arr[idx], idx); 87 | } 88 | 89 | uint[3] arr = [0, 1, 2]; 90 | function check_storage_slot(uint idx) public { 91 | vm.assume(idx == 0 || idx == 1 || idx == 2); 92 | assertEq(arr[idx], idx); 93 | } 94 | 95 | function check_calldata_index(bytes calldata data, uint idx) external { 96 | vm.assume(idx == 0 || idx == 1 || idx == 2); 97 | 98 | vm.assume(data.length > 2); 99 | 100 | vm.assume(data[0] == bytes1(uint8(0))); 101 | vm.assume(data[1] == bytes1(uint8(1))); 102 | vm.assume(data[2] == bytes1(uint8(2))); 103 | 104 | assertEq(data[idx], bytes1(uint8(idx))); 105 | } 106 | 107 | function check_memory_size(uint idx) public { 108 | uint[] memory arr; 109 | 110 | vm.assume(idx == 0 || idx == 1 || idx == 2 || idx == 3); 111 | 112 | arr = new uint[](idx); 113 | for (uint i = 0; i < idx; i++) { 114 | arr[i] = i; 115 | } 116 | 117 | assertEq(arr.length, idx); 118 | if (idx > 0) assertEq(arr[0], 0); 119 | if (idx > 1) assertEq(arr[1], 1); 120 | if (idx > 2) assertEq(arr[2], 2); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /tests/regression/test/Console.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import {console2} from "forge-std/console2.sol"; 6 | 7 | contract ConsoleTest is Test { 8 | function check_log_uint() public view { 9 | console2.log("this is 0:", uint256(0)); 10 | console2.log("this is 1:", uint256(1)); 11 | 12 | console2.log(uint256(0)); 13 | console2.log(uint256(1)); 14 | 15 | console2.logUint(0); 16 | console2.logUint(1); 17 | } 18 | 19 | function check_log_int() public view { 20 | console2.log("this is -1:", -1); 21 | console2.log("this is 1:", int256(1)); 22 | 23 | console2.log(-1); 24 | console2.log(int256(1)); 25 | 26 | console2.logInt(-1); 27 | console2.logInt(int256(1)); 28 | } 29 | 30 | function check_log_bytes() public view { 31 | bytes memory hello = "hello"; 32 | bytes memory empty = ""; 33 | console2.log("this is hello (bytes):"); 34 | console2.logBytes(hello); 35 | console2.log("this is empty bytes:"); 36 | console2.logBytes(empty); 37 | } 38 | 39 | function check_log_bytes32() public view { 40 | console2.log("this is keccak256(hello):"); 41 | console2.logBytes32(keccak256("hello")); 42 | 43 | console2.log("this is keccak256():"); 44 | console2.logBytes32(keccak256("")); 45 | } 46 | 47 | function check_log_address() public view { 48 | console2.log("this is address(0):", address(0)); 49 | console2.log("this is address(this):", address(this)); 50 | 51 | console2.log(address(0)); 52 | console2.log(address(this)); 53 | } 54 | 55 | function check_log_bool() public view { 56 | console2.log("this is true:", true); 57 | console2.log("this is false:", false); 58 | 59 | console2.log(true); 60 | console2.log(false); 61 | } 62 | 63 | function check_log_string() public view { 64 | string memory hello = "hello"; 65 | string memory empty = ""; 66 | console2.log("this is hello (string):", hello); 67 | console2.log("this is empty string:", empty); 68 | 69 | console2.log(hello); 70 | console2.log(empty); 71 | } 72 | 73 | function check_log_undecodable_string() public view { 74 | bytes memory badBytes = hex"ff"; 75 | string memory bad = string(badBytes); 76 | console2.log("this is a string that won't decode to utf-8:", bad); 77 | } 78 | 79 | function check_log_unsupported() public { 80 | console2._sendLogPayload(abi.encodeWithSignature("doesNotExist()")); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/regression/test/Const.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/Const.sol"; 6 | 7 | contract ConstTest is Const { 8 | function check_Const() public pure { 9 | assert(const == 11); 10 | } 11 | } 12 | 13 | contract ConstTestTest is Const, Test { 14 | function check_Const() public { 15 | assertEq(const, 11); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/regression/test/Constructor.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract C { 5 | uint public codesize_; 6 | uint public extcodesize_; 7 | 8 | constructor () { 9 | setCodesize(); 10 | } 11 | 12 | function setCodesize() public { 13 | assembly { 14 | sstore(codesize_.slot, codesize()) 15 | sstore(extcodesize_.slot, extcodesize(address())) 16 | } 17 | } 18 | } 19 | 20 | contract ConstructorTest { 21 | C c; 22 | 23 | function setUp() public { 24 | c = new C(); 25 | } 26 | 27 | function check_constructor() public { 28 | assert(c.codesize_() > 0); // contract creation bytecode size 29 | assert(c.extcodesize_() == 0); // code is not yet deposited in the network state during constructor() 30 | } 31 | 32 | function check_setCodesize() public { 33 | uint creation_codesize = c.codesize_(); 34 | 35 | c.setCodesize(); 36 | 37 | assert(c.codesize_() > 0); // deployed bytecode size 38 | assert(c.extcodesize_() > 0); // deployed bytecode size 39 | assert(c.codesize_() == c.extcodesize_()); 40 | 41 | assert(c.codesize_() < creation_codesize); // deployed bytecode is smaller than creation bytecode 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/regression/test/Counter.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "../src/Counter.sol"; 5 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 6 | 7 | /// @custom:halmos --loop 4 8 | contract CounterTest is Counter, SymTest { 9 | function setUp() public { 10 | svm.enableSymbolicStorage(address(this)); 11 | } 12 | 13 | function check_set(uint n) public { 14 | set(n); 15 | assert(cnt == n); 16 | } 17 | 18 | function check_inc() public { 19 | uint oldCnt = cnt; 20 | inc(); 21 | assert(cnt > oldCnt); 22 | assert(cnt == oldCnt + 1); 23 | } 24 | 25 | function check_incOpt() public { 26 | uint oldCnt = cnt; 27 | require(cnt < type(uint).max); 28 | incOpt(); 29 | assert(cnt > oldCnt); 30 | assert(cnt == oldCnt + 1); 31 | } 32 | 33 | function check_incBy(uint n) public { 34 | uint oldCnt = cnt; 35 | incBy(n); 36 | assert(cnt < oldCnt || cnt == oldCnt + n); // cnt >= oldCnt ==> cnt == oldCnt + n 37 | } 38 | 39 | function specLoopFor(uint n) public { 40 | uint oldCnt = cnt; 41 | loopFor(n); 42 | assert(cnt >= oldCnt); 43 | assert(cnt == oldCnt + n); 44 | } 45 | function check_loopFor(uint8 k) public { 46 | specLoopFor(k); 47 | } 48 | 49 | function specLoopWhile(uint n) public { 50 | uint oldCnt = cnt; 51 | loopWhile(n); 52 | assert(cnt >= oldCnt); 53 | assert(cnt == oldCnt + n); 54 | } 55 | function check_loopWhile(uint8 k) public { 56 | specLoopWhile(k); 57 | } 58 | 59 | function specLoopDoWhile(uint n) public { 60 | uint oldCnt = cnt; 61 | loopDoWhile(n); 62 | assert(cnt > oldCnt); 63 | if (n == 0) assert(cnt == oldCnt + 1); 64 | else assert(cnt == oldCnt + n); 65 | } 66 | function check_loopDoWhile(uint8 k) public { 67 | specLoopDoWhile(k); 68 | } 69 | 70 | function check_loopConst() public { 71 | uint oldCnt = cnt; 72 | loopConst(); 73 | assert(cnt >= oldCnt); 74 | assert(cnt == oldCnt + 2); 75 | } 76 | 77 | function check_loopConstIf() public { 78 | uint oldCnt = cnt; 79 | loopConstIf(); 80 | assert(cnt >= oldCnt); 81 | assert(cnt <= oldCnt + 4); 82 | } 83 | 84 | function specSetSum(uint[2] memory arr) public { 85 | setSum(arr); 86 | assert(cnt == arr[0] + arr[1]); 87 | } 88 | function check_setSum(uint248 a, uint248 b) public { 89 | specSetSum([uint(a), b]); 90 | } 91 | 92 | function check_setString(uint, string memory s, uint, string memory r, uint) public { 93 | uint oldCnt = cnt; 94 | setString(s); 95 | setString(r); 96 | assert(cnt == oldCnt + bytes(s).length + bytes(r).length); 97 | } 98 | 99 | function check_foo(uint a, uint b, uint c, uint d) public { 100 | uint oldCnt = cnt; 101 | foo(a, b, c, d); 102 | assert(cnt == oldCnt + 4); 103 | } 104 | 105 | function check_div_1(uint x, uint y) public pure { 106 | if (y > 0) { 107 | assert(x / y <= x); 108 | } 109 | } 110 | 111 | function check_div_2(uint x, uint y) public pure { 112 | if (y > 0) { 113 | assert(x / y == x / y); 114 | } 115 | } 116 | 117 | function check_mulDiv(uint x, uint y) public pure { 118 | unchecked { 119 | if (x > 0 && y > 0) { 120 | uint z = x * y; 121 | if (z / x == y) { 122 | assert(z / x == y); 123 | //assert(z / y == x); // smt failed to solve 124 | } 125 | } 126 | } 127 | } 128 | 129 | /* TODO: support checkFail prefix 130 | function checkFail() public pure { 131 | require(false); 132 | // deadcode 133 | } 134 | */ 135 | } 136 | -------------------------------------------------------------------------------- /tests/regression/test/Create.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/Create.sol"; 6 | 7 | contract CreateTest is Test { 8 | Create public create; 9 | 10 | function setUp() public { 11 | create = new Create(0x220E); 12 | } 13 | 14 | /* TODO: support checkFail prefix 15 | function checkFail_setUp() public { 16 | assertEq(create.value(), 0); 17 | } 18 | */ 19 | 20 | function check_set(uint x) public { 21 | create.set(x); 22 | assertEq(create.value(), x); 23 | } 24 | 25 | function check_immutable() public { 26 | assertEq(create.halmos(), 0x220E); 27 | } 28 | 29 | function check_initialized() public { 30 | assertEq(create.initialized(), 7); 31 | } 32 | 33 | function check_const() public { 34 | assertEq(create.const(), 11); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/regression/test/Deal.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract DealTest is Test { 7 | uint256 constant MAX_ETH = 1 << 128; 8 | C c; 9 | 10 | function check_deal_1(address payable receiver, uint amount) public { 11 | vm.deal(receiver, amount); 12 | assert(receiver.balance == amount); 13 | } 14 | 15 | function check_deal_2(address payable receiver, uint amount1, uint amount2) public { 16 | vm.deal(receiver, amount1); 17 | vm.deal(receiver, amount2); // reset the balance, not increasing 18 | assert(receiver.balance == amount2); 19 | } 20 | 21 | function check_deal_new() public { 22 | vm.deal(address(this), 3 ether); 23 | 24 | c = new C{value: 3 ether}(); 25 | 26 | assertGe(address(c).balance, 3 ether); // it is possible to send ether to c before it is created 27 | assert(address(this).balance == 0 ether); 28 | } 29 | 30 | // raises an error 31 | function check_deal_over_max_eth() public { 32 | uint256 too_much = MAX_ETH + 1 ether; 33 | vm.deal(address(this), too_much); 34 | assertNotEq(address(this).balance, too_much); 35 | } 36 | 37 | // raises an error 38 | function check_deal_over_max_eth(address addr) public { 39 | uint256 too_much = MAX_ETH + 1 ether; 40 | vm.deal(addr, too_much); 41 | assertLe(addr.balance, MAX_ETH); 42 | } 43 | 44 | function check_deal_over_max_eth(uint256 value) public { 45 | // keep the value symbolic, but guarantee the value is greater than MAX_ETH 46 | vm.deal(address(this), 1 << 255 | value); 47 | assertLe(address(this).balance, MAX_ETH); 48 | } 49 | 50 | function check_deal_over_max_eth(address addr, uint256 value) public { 51 | // keep the value symbolic, but guarantee the value is greater than MAX_ETH 52 | vm.deal(addr, 1 << 255 | value); 53 | assertLe(addr.balance, MAX_ETH); 54 | } 55 | 56 | function check_concretized_transfer_over_max_eth(bytes memory data) public { 57 | address payable to = payable(address(0x42)); 58 | vm.deal(to, MAX_ETH); 59 | 60 | // this should be concretized to: 61 | // - a path where data.length == 0, so the transfer succeeds and the assert passes 62 | // - one or more paths where data.length > 0, so the assert fails 63 | // (more precisely the balance update/retrieval will fail) 64 | to.transfer(data.length); 65 | assertLe(to.balance, MAX_ETH); 66 | } 67 | 68 | function check_implicit_transfer_over_max_eth(uint256 x) public { 69 | vm.assume(x <= address(this).balance); 70 | 71 | address payable to = payable(address(0x42)); 72 | vm.deal(to, MAX_ETH - 1); 73 | 74 | to.transfer(x); 75 | 76 | // if we don't load to's balance, the constraint on x is not added to the path 77 | console.log("to.balance", to.balance); 78 | 79 | // can x be greater than 1? nope! so this is a PASS 80 | assertLe(x, 1); 81 | } 82 | 83 | function check_deal_under_max_eth_mixed(address addr) public { 84 | uint256 large_but_ok = MAX_ETH - 1 ether; 85 | vm.deal(address(this), large_but_ok); 86 | 87 | // cex can be found 88 | assertLe(addr.balance, MAX_ETH / 2); 89 | } 90 | } 91 | 92 | contract C { 93 | constructor() payable { } 94 | } 95 | -------------------------------------------------------------------------------- /tests/regression/test/Getter.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | // from https://github.com/a16z/halmos/issues/82 5 | 6 | /// @custom:halmos --storage-layout=generic 7 | contract GetterTest { 8 | uint256[3] public v; 9 | uint w; 10 | 11 | function check_Getter(uint256 i) public view { 12 | assert(v[i] >= 0); 13 | } 14 | 15 | function check_externalGetter(uint256 i) public view { 16 | assert(this.v(i) >= 0); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/regression/test/Invalid.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity ^0.5.2; 3 | 4 | contract OldCompilerTest { 5 | 6 | function check_assert(uint x) public pure { 7 | if (x == 0) return; 8 | assert(false); // old compiler versions don't revert with panic; instead, they run invalid opcode, which halmos ignores, resulting in no error here. 9 | } 10 | 11 | function check_myAssert(uint x) public pure { 12 | if (x == 0) return; 13 | myAssert(false); // you can use your own assertion that panic-reverts if assertion fails, when using halmos for old version code. 14 | } 15 | 16 | function myAssert(bool cond) internal pure { 17 | if (!cond) { 18 | assembly { 19 | mstore(0x00, 0x4e487b71) 20 | mstore(0x20, 0x01) 21 | revert(0x1c, 0x24) 22 | } 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /tests/regression/test/Invariant.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; 5 | 6 | import "forge-std/Test.sol"; 7 | 8 | contract C { 9 | uint public num; 10 | 11 | function inc(uint x) public { 12 | require(x <= 2); 13 | num += x; 14 | } 15 | } 16 | 17 | 18 | contract InvariantTest is Test { 19 | C c; 20 | 21 | function setUp() public { 22 | c = new C(); 23 | } 24 | 25 | function invariant_1() public { 26 | console.log(c.num()); 27 | assertLe(c.num(), 4); 28 | } 29 | 30 | function invariant_2() public { 31 | assert(c.num() != 3); 32 | } 33 | } 34 | 35 | contract InvariantProxyTest is Test { 36 | C impl = new C(); 37 | C c; 38 | 39 | function setUp() public { 40 | c = C(Clones.clone(address(impl))); 41 | } 42 | 43 | function invariant_proxy_1() public { 44 | console.log(c.num()); 45 | } 46 | 47 | function invariant_proxy_2() public { 48 | assert(c.num() != 3); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/regression/test/InvariantProbes.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract WithProbes { 7 | uint public num; 8 | bool public unlocked; 9 | 10 | // should always trigger (but be reported only once) 11 | function probe0() public { 12 | assert(false); 13 | } 14 | 15 | // requires some exploration before triggering 16 | function probe1() public { 17 | if (num == 1) { 18 | assert(false); 19 | } 20 | 21 | if (num > 1) { 22 | unlocked = true; 23 | } 24 | } 25 | 26 | // requires state change produced by probe1() to trigger 27 | function probe2() public { 28 | if (unlocked) { 29 | assert(false); 30 | } 31 | } 32 | 33 | function inc() public { 34 | num += 1; 35 | } 36 | } 37 | 38 | contract InvariantProbesTest is Test { 39 | WithProbes c; 40 | 41 | function setUp() public { 42 | c = new WithProbes(); 43 | } 44 | 45 | // XFAIL: we should report failures when asserts are hit in the target contract 46 | /// @custom:halmos --invariant-depth 4 47 | function invariant_probes_found() public { 48 | assertGe(c.num(), 0); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/regression/test/InvariantSender.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract C { 7 | uint public num; 8 | address public owner; 9 | 10 | constructor() { 11 | owner = msg.sender; 12 | } 13 | 14 | function inc() external { 15 | require(msg.sender == owner); 16 | num++; 17 | } 18 | } 19 | 20 | abstract contract InvariantSenderTest is Test { 21 | C c; 22 | 23 | function invariant_num() public { 24 | assertEq(c.num(), 0); 25 | } 26 | } 27 | 28 | contract InvariantSenderTest_non_owner_1 is InvariantSenderTest { 29 | function setUp() public { 30 | c = new C(); 31 | 32 | targetSender(address(0xbeef)); 33 | } 34 | } 35 | 36 | contract InvariantSenderTest_non_owner_2 is InvariantSenderTest { 37 | function setUp() public { 38 | c = new C(); 39 | 40 | targetSender(address(0xdead)); 41 | targetSender(address(0xbeef)); 42 | } 43 | } 44 | 45 | contract InvariantSenderTest_owner is InvariantSenderTest { 46 | function setUp() public { 47 | c = new C(); 48 | 49 | targetSender(address(this)); 50 | } 51 | } 52 | 53 | contract InvariantSenderTest_all is InvariantSenderTest { 54 | function setUp() public { 55 | c = new C(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/regression/test/InvariantSender2.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | address constant user1 = address(0x11); 7 | address constant user2 = address(0x22); 8 | address constant user3 = address(0x33); 9 | 10 | contract C { 11 | uint public num1; 12 | uint public num2; 13 | uint public num3; 14 | 15 | constructor() { 16 | } 17 | 18 | function inc1() external { 19 | require(msg.sender == user1); 20 | num1++; 21 | } 22 | 23 | function inc2() external { 24 | require(msg.sender == user2); 25 | num2++; 26 | } 27 | 28 | function inc3() external { 29 | require(msg.sender == user3); 30 | num3++; 31 | } 32 | } 33 | 34 | abstract contract InvariantSenderTest is Test { 35 | C c; 36 | 37 | function invariant_num1() public { 38 | assertEq(c.num1(), 0); 39 | } 40 | 41 | function invariant_num2() public { 42 | assertEq(c.num2(), 0); 43 | } 44 | 45 | function invariant_num3() public { 46 | assertEq(c.num3(), 0); 47 | } 48 | } 49 | 50 | contract InvariantSenderTest_excludeSender_empty is InvariantSenderTest { 51 | function setUp() public { 52 | c = new C(); 53 | 54 | // sender: * 55 | } 56 | } 57 | 58 | contract InvariantSenderTest_excludeSender_0 is InvariantSenderTest { 59 | function setUp() public { 60 | c = new C(); 61 | 62 | targetSender(user1); 63 | 64 | excludeSender(user1); 65 | 66 | // sender: * - {user1} 67 | } 68 | } 69 | 70 | contract InvariantSenderTest_excludeSender_1 is InvariantSenderTest { 71 | function setUp() public { 72 | c = new C(); 73 | 74 | targetSender(user1); 75 | 76 | targetSender(user2); 77 | 78 | excludeSender(user2); 79 | 80 | // sender: {user1} 81 | } 82 | } 83 | 84 | contract InvariantSenderTest_excludeSender_2 is InvariantSenderTest { 85 | function setUp() public { 86 | c = new C(); 87 | 88 | targetSender(user1); 89 | 90 | excludeSender(user2); 91 | 92 | targetSender(user2); 93 | 94 | // sender: {user1} 95 | } 96 | } 97 | 98 | contract InvariantSenderTest_excludeSender_3 is InvariantSenderTest { 99 | function setUp() public { 100 | c = new C(); 101 | 102 | targetSender(user1); 103 | 104 | excludeSender(user1); 105 | 106 | excludeSender(user2); 107 | 108 | // sender: * - {user1, user2} 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/regression/test/InvariantTimestamp.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "halmos-cheatcodes/SymTest.sol"; 6 | 7 | contract TimeKeeper { 8 | mapping(address => uint256) public registered; 9 | uint256 public numRegistrations; 10 | 11 | function register() public { 12 | registered[msg.sender] = block.timestamp; 13 | numRegistrations++; 14 | } 15 | 16 | function elapsed(address sender) public view returns (uint256) { 17 | uint256 registrationTime = registered[sender]; 18 | if (registrationTime == 0) { 19 | return 0; 20 | } 21 | 22 | return block.timestamp - registrationTime; 23 | } 24 | } 25 | 26 | contract InvariantTimestampTest is Test, SymTest { 27 | TimeKeeper timeKeeper; 28 | uint256 startTime; 29 | 30 | function setUp() public { 31 | timeKeeper = new TimeKeeper(); 32 | 33 | // by default, the starting timestamp is 1 34 | // we can start with an arbitrary timestamp by warping in setUp() 35 | startTime = svm.createUint(64, "startTime"); 36 | vm.warp(startTime); 37 | } 38 | 39 | // we expect a PASS (this checks that the warp in setUp() is working) 40 | function check_timestamp_setup() external view { 41 | assertEq(startTime, block.timestamp); 42 | } 43 | 44 | // XFAIL -- this checks that the warp in setUp() is working 45 | function invariant_timestamp_setup() external view { 46 | assertEq(1, block.timestamp); 47 | } 48 | 49 | // XFAIL -- this checks that time can change during invariant testing 50 | function invariant_timestamp_can_change() external view { 51 | assertEq(startTime, block.timestamp); 52 | } 53 | 54 | // XFAIL -- this checks that time can remain the same during invariant testing 55 | function invariant_timestamp_does_not_have_to_change() external view { 56 | // we want to avoid the check at depth=0 (running the invariant on the setUp state itself) 57 | if (timeKeeper.numRegistrations() > 0) { 58 | assertNotEq(startTime, block.timestamp); 59 | } 60 | } 61 | 62 | // we expect a PASS here 63 | function invariant_timestamp_can_only_move_forward() external view { 64 | assertGe(block.timestamp, startTime); 65 | } 66 | 67 | // we expect a PASS here 68 | function invariant_timestamp_is_bounded() external view { 69 | assertLe(block.timestamp, 2**64); 70 | } 71 | 72 | // XFAIL -- this checks that time can change during invariant testing (via external contract) 73 | // (a PASS would mean that time can not move forward) 74 | function invariant_timestamp_timekeeper(address sender) external view { 75 | assertEq(0, timeKeeper.elapsed(sender)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/regression/test/Library.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | library Math { 5 | function add(uint x, uint y) public pure returns (uint) { 6 | return _add(x,y); 7 | } 8 | 9 | function _add(uint x, uint y) internal pure returns (uint) { 10 | unchecked { 11 | return x + y; 12 | } 13 | } 14 | } 15 | 16 | contract LibraryTest { 17 | function check_add(uint x, uint y) public pure { 18 | unchecked { 19 | assert(Math._add(x,y) == x+y); 20 | /* TODO: support public library functions (library linking) 21 | assert(Math.add(x,y) == x+y); 22 | */ 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/regression/test/LibraryLinking.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | library Lib { 5 | function foo() public pure returns (uint) { return 1; } 6 | function bar() internal pure returns (uint) { return 2; } 7 | } 8 | 9 | contract LibTest { 10 | function check_foo() public pure { 11 | assert(Lib.foo() == 1); // library linking placeholder error 12 | } 13 | } 14 | 15 | contract LibTest2 { 16 | function check_bar() public pure { 17 | assert(Lib.bar() == 2); // this is fine because internal library functions are inlined 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/regression/test/List.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/List.sol"; 6 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 7 | 8 | contract ListTest is SymTest, Test, List { 9 | function setUp() public { 10 | svm.enableSymbolicStorage(address(this)); 11 | } 12 | 13 | function check_add(uint x) public { 14 | uint oldSize = arr.length; 15 | vm.assume(oldSize < type(uint).max); 16 | add(x); 17 | uint newSize = arr.length; 18 | assert(oldSize < newSize); 19 | assert(oldSize + 1 == newSize); 20 | assert(arr[newSize-1] == x); 21 | } 22 | 23 | function check_remove() public { 24 | uint oldSize = arr.length; 25 | vm.assume(oldSize > 0); 26 | remove(); 27 | uint newSize = arr.length; 28 | assert(oldSize > newSize); 29 | assert(oldSize == newSize + 1); 30 | } 31 | 32 | function check_set(uint i, uint x) public { 33 | vm.assume(i < arr.length); 34 | set(i, x); 35 | assert(arr[i] == x); 36 | } 37 | } 38 | 39 | contract ListTestTest is SymTest, Test { 40 | List list; 41 | 42 | function setUp() public { 43 | list = new List(); 44 | list.add(1); 45 | svm.enableSymbolicStorage(address(list)); 46 | } 47 | 48 | function check_add(uint x) public { 49 | uint oldSize = list.size(); 50 | vm.assume(oldSize < type(uint).max); 51 | list.add(x); 52 | uint newSize = list.size(); 53 | assertLt(oldSize, newSize, "oldSize < newSize"); 54 | assertEq(oldSize + 1, newSize, "oldSize + 1 == newSize"); 55 | assertEq(list.arr(newSize - 1), x, "list.arr(newSize - 1) == x"); 56 | } 57 | 58 | function check_remove() public { 59 | uint oldSize = list.size(); 60 | vm.assume(oldSize > 0); 61 | list.remove(); 62 | uint newSize = list.size(); 63 | assert(oldSize > newSize); 64 | assert(oldSize == newSize + 1); 65 | } 66 | 67 | function check_set(uint i, uint x) public { 68 | vm.assume(i < list.size()); 69 | list.set(i, x); 70 | assert(list.arr(i) == x); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/regression/test/Natspec.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract NatspecTestNone { 5 | Loop l; 6 | 7 | function setUp() public { 8 | l = new Loop(); 9 | } 10 | 11 | function check_Loop2(uint n) public view { 12 | assert(l.iter(n) <= 2); // pass // default 13 | } 14 | function check_Loop2Fail(uint n) public view { 15 | assert(l.iter(n) <= 1); // fail // default 16 | } 17 | } 18 | 19 | /// @custom:halmos --loop 3 20 | contract NatspecTestContract { 21 | Loop l; 22 | 23 | function setUp() public { 24 | l = new Loop(); 25 | } 26 | 27 | function check_Loop3(uint n) public view { 28 | assert(l.iter(n) <= 3); // pass // inherited from contract 29 | } 30 | function check_Loop3Fail(uint n) public view { 31 | assert(l.iter(n) <= 2); // fail // inherited from contract 32 | } 33 | } 34 | 35 | contract NatspecTestSetup { 36 | Loop l; 37 | 38 | /// @custom:halmos --loop 3 39 | function setUp() public { 40 | l = new Loop(); 41 | } 42 | 43 | function check_Loop2(uint n) public view { 44 | assert(l.iter(n) <= 2); // pass // default 45 | } 46 | function check_Loop2Fail(uint n) public view { 47 | assert(l.iter(n) <= 1); // fail // default 48 | } 49 | } 50 | 51 | contract NatspecTestFunction { 52 | Loop l; 53 | 54 | function setUp() public { 55 | l = new Loop(); 56 | } 57 | 58 | /// @custom:halmos --loop 3 59 | function check_Loop3(uint n) public view { 60 | assert(l.iter(n) <= 3); // pass 61 | } 62 | /// @custom:halmos --loop 3 63 | function check_Loop3Fail(uint n) public view { 64 | assert(l.iter(n) <= 2); // fail 65 | } 66 | 67 | function check_Loop2(uint n) public view { 68 | assert(l.iter(n) <= 2); // pass // default 69 | } 70 | function check_Loop2Fail(uint n) public view { 71 | assert(l.iter(n) <= 1); // fail // default 72 | } 73 | } 74 | 75 | /// @custom:halmos --loop 4 76 | contract NatspecTestOverwrite { 77 | Loop l; 78 | 79 | function setUp() public { 80 | l = new Loop(); 81 | } 82 | 83 | function check_Loop4(uint n) public view { 84 | assert(l.iter(n) <= 4); // pass // inherited from contract 85 | } 86 | function check_Loop4Fail(uint n) public view { 87 | assert(l.iter(n) <= 3); // fail // inherited from contract 88 | } 89 | 90 | /// @custom:halmos --loop 3 91 | function check_Loop3(uint n) public view { 92 | assert(l.iter(n) <= 3); // pass // overwrite 93 | } 94 | /// @custom:halmos --loop 3 95 | function check_Loop3Fail(uint n) public view { 96 | assert(l.iter(n) <= 2); // fail // overwrite 97 | } 98 | } 99 | 100 | contract Loop { 101 | function iter(uint n) public pure returns (uint) { 102 | uint cnt = 0; 103 | for (uint i = 0; i < n; i++) { 104 | cnt++; 105 | } 106 | return cnt; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/regression/test/Opcode.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | library Opcode { 7 | function SIGNEXTEND(uint size, uint value) internal pure returns (uint result) { 8 | assembly { 9 | result := signextend(size, value) 10 | } 11 | } 12 | } 13 | 14 | contract OpcodeTest is Test { 15 | 16 | function check_SIGNEXTEND(uint value) public { 17 | _check_SIGNEXTEND(0, value); 18 | _check_SIGNEXTEND(1, value); 19 | _check_SIGNEXTEND(2, value); 20 | _check_SIGNEXTEND(30, value); 21 | _check_SIGNEXTEND(31, value); 22 | _check_SIGNEXTEND(32, value); 23 | _check_SIGNEXTEND(33, value); 24 | } 25 | 26 | /* TODO: support symbolic size 27 | function check_SIGNEXTEND(uint size, uint value) public { 28 | _check_SIGNEXTEND(size, value); 29 | } 30 | */ 31 | 32 | function _check_SIGNEXTEND(uint size, uint value) public { 33 | uint result1 = Opcode.SIGNEXTEND(size, value); 34 | uint result2; 35 | if (size > 31) { 36 | result2 = value; 37 | } else { 38 | uint testbit = size * 8 + 7; 39 | uint signbit = (1 << testbit); 40 | if ((value & signbit) > 0) { 41 | result2 = value | (type(uint).max - signbit + 1); 42 | } else { 43 | result2 = value & (signbit - 1); 44 | } 45 | } 46 | assertEq(result1, result2); 47 | } 48 | 49 | function check_PUSH0() public { 50 | // target bytecode is 0x365f5f37365ff3 51 | // 36 CALLDATASIZE 52 | // 5F PUSH0 53 | // 5F PUSH0 54 | // 37 CALLDATACOPY -> copies calldata at mem[0..calldatasize] 55 | 56 | // 36 CALLDATASIZE 57 | // 5F PUSH0 58 | // F3 RETURN -> returns mem[0..calldatasize] 59 | 60 | // a tiny deployer (that uses PUSH0), to deploy the above bytecode 61 | uint256 deployCode = 0x66365f5f37365ff35f5260076019f3; 62 | address target; 63 | assembly { 64 | mstore(0, deployCode) 65 | target := create(/* value */ 0, /* offset */ 0x11, /* size */ 15) 66 | } 67 | 68 | (bool success, bytes memory result) = target.call(bytes("hello PUSH0")); 69 | assertTrue(success); 70 | assertEq(string(result), "hello PUSH0"); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/regression/test/Proxy.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.19; 2 | 3 | import "forge-std/Test.sol"; 4 | 5 | import {ERC1967Proxy} from "openzeppelin/proxy/ERC1967/ERC1967Proxy.sol"; 6 | 7 | contract C { 8 | uint public num; 9 | function foo(uint x) public payable returns (address, uint, address) { 10 | num = x; 11 | return (msg.sender, msg.value, address(this)); 12 | } 13 | } 14 | 15 | contract ProxyTest is Test { 16 | C cImpl; 17 | C c; 18 | 19 | function setUp() public { 20 | cImpl = new C(); 21 | c = C(address(new ERC1967Proxy(address(cImpl), ""))); 22 | } 23 | 24 | function check_foo(uint x, uint fund, address caller) public { 25 | vm.deal(caller, fund); 26 | vm.prank(caller); 27 | (address msg_sender, uint msg_value, address target) = c.foo{ value: fund }(x); 28 | assert(msg_sender == caller); 29 | assert(msg_value == fund); 30 | assert(target == address(c)); 31 | 32 | assert(c.num() == x); 33 | assert(cImpl.num() == 0); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/regression/test/Revert.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | // from https://github.com/a16z/halmos/issues/109 5 | 6 | import "forge-std/Test.sol"; 7 | 8 | contract A { } 9 | 10 | contract C { 11 | uint256 public num; 12 | 13 | function set1(uint256 x) public { 14 | num = x; 15 | revert("blah"); 16 | } 17 | 18 | function set2(uint256 x) public { 19 | revert("blah"); 20 | num = x; 21 | } 22 | 23 | function deposit(bool paused) public payable { 24 | if (paused) revert("paused"); 25 | } 26 | 27 | function create() public { 28 | A a = new A(); 29 | revert(string(abi.encode(a))); 30 | } 31 | } 32 | 33 | contract CTest is Test { 34 | C c; 35 | 36 | function setUp() public { 37 | c = new C(); 38 | } 39 | 40 | function check_Revert1(uint256 x) public { 41 | require(x != 0); 42 | (bool result, ) = address(c).call(abi.encodeWithSignature("set1(uint256)", x)); 43 | assert(!result); 44 | assert(c.num() != x); 45 | } 46 | 47 | function check_Revert2(uint256 x) public { 48 | require(x != 0); 49 | (bool result, ) = address(c).call(abi.encodeWithSignature("set2(uint256)", x)); 50 | assert(!result); 51 | assert(c.num() != x); 52 | } 53 | 54 | function check_RevertBalance_Known(bool paused, uint256 amount) public { 55 | vm.deal(address(this), amount); 56 | vm.deal(address(c), 0); 57 | 58 | (bool result,) = address(c).call{value: amount}(abi.encodeWithSignature("deposit(bool)", paused)); 59 | 60 | if (result) { 61 | assert(!paused); 62 | assert(address(this).balance == 0); 63 | assert(address(c).balance == amount); 64 | } else { 65 | assert(paused); 66 | assert(address(this).balance == amount); 67 | assert(address(c).balance == 0); 68 | } 69 | } 70 | 71 | function check_RevertBalance_Unknown(uint256 balance, uint256 amount) public { 72 | vm.deal(address(this), balance); 73 | vm.assume(amount > balance); 74 | 75 | (bool result, ) = address(42).call{value: amount}(""); 76 | 77 | assert(!result); 78 | assertEq(address(this).balance, balance); 79 | assertEq(address(42).balance, 0); 80 | } 81 | 82 | function check_BalanceTransfer_Known(uint256 balance, uint256 amount) public { 83 | // balance and amount are unconstrained, so could fail, could succeed 84 | vm.deal(address(this), balance); 85 | 86 | (bool success, ) = address(c).call{value: amount}(abi.encodeWithSignature("deposit(bool)", false)); 87 | 88 | // we are looking for a counterexample here 89 | // i.e., halmos should find the case amount > balance 90 | assert(success); 91 | } 92 | 93 | function check_BalanceTransfer_Unknown(uint256 balance, uint256 amount) public { 94 | // balance and amount are unconstrained, so could fail, could succeed 95 | vm.deal(address(this), balance); 96 | 97 | (bool success, ) = address(42).call{value: amount}(""); 98 | 99 | // we are looking for a counterexample here 100 | // i.e., halmos should find the case amount > balance 101 | assert(success); 102 | } 103 | 104 | function codesize(address x) internal view returns (uint256 size) { 105 | assembly { size := extcodesize(x) } 106 | } 107 | 108 | function check_RevertCode(address x) public { 109 | uint256 oldSize = codesize(x); 110 | try c.create() { 111 | } catch Error(string memory s) { 112 | address a = abi.decode(bytes(s), (address)); 113 | uint256 size = codesize(a); 114 | vm.assume(a == x); 115 | assert(size == oldSize); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/regression/test/Setup.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract SetupTest is Test { 7 | address[] public users; 8 | uint256 public constant size = 3; 9 | 10 | function setUp() public { 11 | users = new address[](size); 12 | users[0] = address(bytes20(keccak256("test"))); 13 | for (uint256 i = 1; i < size - 1; i++) { 14 | users[i] = address(uint160(users[i - 1]) + 1); 15 | } 16 | } 17 | 18 | function check_True() public { 19 | assertEq(users[0], address(bytes20(keccak256("test")))); 20 | assertEq(users[1], address(uint160(users[0]) + 1)); 21 | assertEq(users[2], address(0)); 22 | } 23 | } 24 | 25 | contract SetupFailTest { 26 | function setUp() public { 27 | revert(); 28 | } 29 | 30 | function check_setUp_Fail1() public { 31 | assert(true); 32 | } 33 | 34 | function check_setUp_Fail2() public { 35 | assert(true); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/regression/test/SetupPlus.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract A { 5 | uint public immutable x; 6 | uint public y; 7 | 8 | constructor(uint _x, uint _y) { 9 | x = _x; 10 | y = _y; 11 | } 12 | } 13 | 14 | contract SetupPlusTest { 15 | A a; 16 | 17 | function setUp() public { 18 | a = new A(11, 200); 19 | } 20 | 21 | // if setUpSymbolic() is provided, Halmos uses setUpSymbolic() instead of setUp(). 22 | // setUpSymbolic() is symbolically executed. 23 | 24 | // if multiple setUpSymbolic() functions are provided, the last one in the lexicographical order will be used. 25 | // e.g., setUpSymbolic(uint256,uint256) is used instead of setUpSymbolic(uint256). 26 | 27 | function setUpSymbolic(uint x, uint y) public { 28 | require(x > 10); 29 | require(y > 100); 30 | a = new A(x, y); 31 | } 32 | 33 | function setUpSymbolic(uint x) public { 34 | a = new A(x, x); 35 | } 36 | 37 | function check_Setup() public view { 38 | assert(a.x() > 10); 39 | assert(a.y() > 100); 40 | } 41 | } 42 | 43 | contract B { 44 | uint public x1; 45 | uint public y1; 46 | uint public x2; 47 | uint public y2; 48 | 49 | struct S { 50 | uint a; 51 | uint b; 52 | } 53 | 54 | constructor(S[] memory lst) { 55 | require(lst.length >= 2); 56 | x1 = lst[0].a; 57 | y1 = lst[0].b; 58 | x2 = lst[1].a; 59 | y2 = lst[1].b; 60 | } 61 | } 62 | 63 | contract SetupPlusTestB { 64 | B b; 65 | uint[4] init; 66 | 67 | function mk() public { 68 | B.S[] memory lst = new B.S[](2); 69 | lst[0] = B.S(init[0], init[1]); 70 | lst[1] = B.S(init[2], init[3]); 71 | b = new B(lst); 72 | } 73 | 74 | function setUp() public { 75 | init[0] = 10; 76 | init[1] = 20; 77 | init[2] = 30; 78 | init[3] = 40; 79 | mk(); 80 | } 81 | 82 | function setUpSymbolic(uint[4] memory _init) public { 83 | init[0] = _init[0]; 84 | init[1] = _init[1]; 85 | init[2] = _init[2]; 86 | init[3] = _init[3]; 87 | mk(); 88 | } 89 | 90 | function check_Setup() public view { 91 | assert(b.x1() == init[0]); 92 | assert(b.y1() == init[1]); 93 | assert(b.x2() == init[2]); 94 | assert(b.y2() == init[3]); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/regression/test/SetupSymbolic.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract SetupSymbolicTest { 5 | function setUpSymbolic(uint x) public pure { 6 | if (x > 0) revert(); // generate multiple setup output states, but only a single success output state 7 | } 8 | 9 | function check_True() public pure { 10 | assert(true); // ensure setUp succeeds 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/regression/test/Sha3.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 6 | 7 | contract Sha3Test is Test, SymTest { 8 | // 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 9 | bytes32 constant EMPTY_HASH = keccak256(""); 10 | 11 | function check_hash() public { 12 | _assert_eq("", ""); 13 | _assert_eq("1", "1"); 14 | 15 | bytes memory data = svm.createBytes(1, "data"); 16 | _assert_eq(data, data); 17 | } 18 | 19 | function check_no_hash_collision_assumption() public { 20 | // assume no hash collisions 21 | 22 | bytes memory data1 = svm.createBytes(1, "data1"); 23 | bytes memory data2 = svm.createBytes(2, "data2"); 24 | _assert_neq(data1, data2); 25 | 26 | bytes memory data32_1 = svm.createBytes(32, "data32_1"); 27 | bytes memory data32_2 = svm.createBytes(32, "data32_2"); 28 | vm.assume(keccak256(data32_1) == keccak256(data32_2)); 29 | assert(data32_1[0] == data32_2[0]); 30 | } 31 | 32 | function check_hash_collision_with_empty() public { 33 | bytes memory data = svm.createBytes(1, "data"); 34 | assertNotEq(keccak256(data), keccak256("")); 35 | } 36 | 37 | function check_empty_hash_value() public { 38 | assertEq(keccak256(""), EMPTY_HASH); 39 | 40 | // TODO: uncomment when we support empty bytes 41 | // bytes memory data = svm.createBytes(0, "data"); 42 | // assertEq(keccak256(data), EMPTY_HASH); 43 | } 44 | 45 | function check_only_empty_bytes_matches_empty_hash(bytes memory data) public { 46 | // empty hash value 47 | vm.assume(keccak256(data) == EMPTY_HASH); 48 | assertEq(data.length, 0); 49 | } 50 | 51 | function check_concrete_keccak_does_not_split_paths() external { 52 | bytes32 hash = keccak256("data"); 53 | uint256 bit = uint256(hash) & 1; 54 | 55 | // this tests that the hash value is concrete 56 | // if it was symbolic, we would split paths and fail in the even case 57 | // (keccak("data") is odd) 58 | if (uint256(hash) & 1 == 0) { 59 | console2.log("even"); 60 | assert(false); 61 | } else { 62 | console2.log("odd"); 63 | assert(true); 64 | } 65 | } 66 | 67 | function check_concrete_keccak_memory_lookup() external { 68 | bytes32 hash = keccak256(abi.encodePacked(uint256(3))); 69 | uint256 bit = uint256(hash) & 1; 70 | 71 | string[] memory x = new string[](2); 72 | x[0] = "even"; 73 | x[1] = "odd"; 74 | 75 | // checks that we don't fail with symbolic memory offset error 76 | console2.log(x[bit]); 77 | } 78 | 79 | function _assert_eq(bytes memory data1, bytes memory data2) internal { 80 | assert(keccak256(data1) == keccak256(data2)); 81 | } 82 | 83 | function _assert_neq(bytes memory data1, bytes memory data2) internal { 84 | assert(keccak256(data1) != keccak256(data2)); 85 | } 86 | 87 | function check_uint256_collision(uint256 x, uint256 y) public { 88 | vm.assume(x != y); 89 | assertNotEq(keccak256(abi.encode(x)), keccak256(abi.encode(y))); 90 | } 91 | 92 | // we assume that the lower 160-bit parts do not collide 93 | // see: https://github.com/a16z/halmos/issues/347 94 | function check_address_collision_pass(uint256 x, uint256 y) public { 95 | vm.assume(x != y); 96 | assertNotEq(to_address(x), to_address(y)); // pass 97 | } 98 | 99 | function to_address(uint256 x) internal pure returns (address) { 100 | return address(uint160(uint256(keccak256(abi.encode(x))))); 101 | } 102 | 103 | function check_uint160_collision_pass(uint256 x, uint256 y) public { 104 | vm.assume(x != y); 105 | assertNotEq(uint160(uint256(keccak256(abi.encode(x)))), uint160(uint256(keccak256(abi.encode(y))))); // pass 106 | } 107 | 108 | // we don't rule out potential collision in the part lower than 160-bit 109 | function check_uint128_collision_fail(uint256 x, uint256 y) public { 110 | vm.assume(x != y); 111 | assertNotEq(uint128(uint256(keccak256(abi.encode(x)))), uint128(uint256(keccak256(abi.encode(y))))); // fail 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/regression/test/SignExtend.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/SignExtend.sol"; 6 | 7 | contract SignExtendTest is SignExtend { 8 | function check_SIGNEXTEND(int16 _x) public pure { 9 | int16 x = changeMySign(_x); 10 | assert(x == -_x); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/regression/test/SmolWETH.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 6 | 7 | contract Dummy {} 8 | 9 | /// @notice don't use, this is very buggy on purpose 10 | contract SmolWETH { 11 | function deposit() external payable { 12 | // bug: we stomp any existing balance 13 | assembly { 14 | sstore(caller(), callvalue()) 15 | } 16 | } 17 | 18 | function withdraw(uint256) external { 19 | assembly { 20 | // revert if msg.value > 0 21 | if gt(callvalue(), 0) { 22 | revert(0, 0) 23 | } 24 | 25 | let amount := sload(caller()) 26 | let success := call(gas(), caller(), amount, 0, 0, 0, 0) 27 | 28 | // bug: we always erase the balance, regardless of transfer success 29 | // bug: we should erase the balance before making the call 30 | sstore(caller(), 0) 31 | } 32 | } 33 | 34 | function balanceOf(address account) external view returns (uint256) { 35 | assembly { 36 | mstore(0, sload(account)) 37 | return(0, 0x20) 38 | } 39 | } 40 | } 41 | 42 | /// @custom:halmos --storage-layout=generic 43 | contract SmolWETHTest is Test, SymTest { 44 | SmolWETH weth; 45 | 46 | function setUp() public { 47 | weth = new SmolWETH(); 48 | } 49 | 50 | function check_deposit_once(address alice, uint256 amount) external { 51 | // fund alice 52 | vm.deal(alice, amount); 53 | 54 | // alice deposits 55 | vm.prank(alice); 56 | weth.deposit{value: amount}(); 57 | 58 | // alice's balance is updated 59 | assertEq(weth.balanceOf(alice), amount); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/regression/test/Solver.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 6 | 7 | contract SolverTest is SymTest, Test { 8 | uint[] numbers; 9 | 10 | function check_dynamic_array_overflow() public { 11 | numbers = new uint[](5); // shouldn't generate loop bounds warning 12 | } 13 | 14 | /// @custom:halmos --solver-timeout-assertion 1 15 | function check_too_many_open_files() public { 16 | // regression test for too many open files error: https://github.com/a16z/halmos/issues/523 17 | // this test simulates a situation where many solver processes are killed due to timeout. 18 | // if file descriptors are not properly closed, this test will fail with "Too many open files" error. 19 | if (svm.createBool("*")) { some_hard_query(); } else { some_hard_query(); } 20 | if (svm.createBool("*")) { some_hard_query(); } else { some_hard_query(); } 21 | if (svm.createBool("*")) { some_hard_query(); } else { some_hard_query(); } 22 | if (svm.createBool("*")) { some_hard_query(); } else { some_hard_query(); } 23 | if (svm.createBool("*")) { some_hard_query(); } else { some_hard_query(); } 24 | if (svm.createBool("*")) { some_hard_query(); } else { some_hard_query(); } 25 | } 26 | 27 | function some_hard_query() internal { 28 | uint a = svm.createUint256("a"); 29 | uint b = svm.createUint256("b"); 30 | uint c = svm.createUint256("c"); 31 | uint n = svm.createUint256("n"); 32 | 33 | vm.assume(n > 2); 34 | // we use a simple arithmetic constraint that is hard enough to solve within the 1ms timeout, 35 | // which is sufficient for testing that solver processes are properly killed and cleaned up. 36 | // note: we avoid exponentiation (a**n + b**n != c**n) as it creates too many execution paths at bytecode level. 37 | assertNotEq(a*n + b*n, c*n); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/regression/test/StaticContexts.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract Dummy {} 7 | 8 | contract StaticContextsTest is Test { 9 | event Log(uint256 x); 10 | 11 | uint256 x; 12 | 13 | function do_sstore() public { 14 | unchecked { 15 | x += 1; 16 | } 17 | } 18 | 19 | function do_log() public { 20 | emit Log(x); 21 | } 22 | 23 | function do_create() public { 24 | new Dummy(); 25 | } 26 | 27 | function do_create2() public { 28 | new Dummy{salt: 0}(); 29 | } 30 | 31 | function do_call_with_value() public { 32 | vm.deal(address(this), 1 ether); 33 | (bool success, ) = payable(address(this)).call{value: 1 ether}(""); 34 | success; // silence warnings 35 | } 36 | 37 | function do_selfdestruct() public { 38 | selfdestruct(payable(address(0))); 39 | } 40 | 41 | function check_sstore_fails() public { 42 | (bool success, ) = address(this).staticcall(abi.encodeWithSignature("do_sstore()")); 43 | assertFalse(success); 44 | } 45 | 46 | function check_log_fails() public { 47 | (bool success, ) = address(this).staticcall(abi.encodeWithSignature("do_log()")); 48 | assertFalse(success); 49 | } 50 | 51 | function check_create_fails() public { 52 | (bool success, ) = address(this).staticcall(abi.encodeWithSignature("do_create()")); 53 | assertFalse(success); 54 | } 55 | 56 | function check_create2_fails() public { 57 | (bool success, ) = address(this).staticcall(abi.encodeWithSignature("do_create2()")); 58 | assertFalse(success); 59 | } 60 | 61 | // TODO: value check not implemented yet 62 | // function check_call_with_value_fails() public { 63 | // (bool success, ) = address(this).staticcall(abi.encodeWithSignature("do_call_with_value()")); 64 | // assertFalse(success); 65 | // } 66 | 67 | // TODO: selfdestruct not implemented yet 68 | // function check_selfdestruct_fails() public { 69 | // (bool success, ) = address(this).staticcall(abi.encodeWithSignature("do_selfdestruct()")); 70 | // assertFalse(success); 71 | // } 72 | } 73 | -------------------------------------------------------------------------------- /tests/regression/test/Storage.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/Storage.sol"; 6 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 7 | 8 | contract StorageTest is Storage, SymTest { 9 | function setUp() public { 10 | svm.enableSymbolicStorage(address(this)); 11 | } 12 | 13 | function check_setMap1(uint k, uint v) public { 14 | setMap1(k, v); 15 | assert(map1[k] == v); 16 | } 17 | 18 | function check_setMap2(uint k1, uint k2, uint v) public { 19 | setMap2(k1, k2, v); 20 | assert(map2[k1][k2] == v); 21 | } 22 | 23 | function check_setMap3(uint k1, uint k2, uint k3, uint v) public { 24 | setMap3(k1, k2, k3, v); 25 | assert(map3[k1][k2][k3] == v); 26 | } 27 | 28 | function check_addArr1(uint v) public { 29 | uint size = arr1.length; 30 | addArr1(v); 31 | assert(arr1.length == size + 1); 32 | assert(arr1[size] == v); 33 | } 34 | 35 | function check_addArr2(uint i, uint v) public { 36 | uint size = arr2[i].length; 37 | addArr2(i, v); 38 | assert(arr2[i].length == size + 1); 39 | assert(arr2[i][size] == v); 40 | } 41 | 42 | function check_addMap1Arr1(uint k, uint v) public { 43 | uint size = map1Arr1[k].length; 44 | addMap1Arr1(k, v); 45 | assert(map1Arr1[k].length == size + 1); 46 | assert(map1Arr1[k][size] == v); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/regression/test/Storage4.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 5 | import {Test} from "forge-std/Test.sol"; 6 | 7 | struct Set { 8 | bytes[] _values; 9 | mapping(bytes => uint256) _indexes; 10 | } 11 | 12 | library EnumerableSet { 13 | function add(Set storage set, bytes calldata value) internal returns (bool) { 14 | if (!contains(set, value)) { 15 | set._values.push(value); 16 | set._indexes[value] = set._values.length; 17 | return true; 18 | } else { 19 | return false; 20 | } 21 | } 22 | 23 | function contains(Set storage set, bytes calldata value) internal view returns (bool) { 24 | return set._indexes[value] != 0; 25 | } 26 | 27 | function length(Set storage set) internal view returns (uint256) { 28 | return set._values.length; 29 | } 30 | 31 | function at(Set storage set, uint256 index) internal view returns (bytes memory) { 32 | return set._values[index]; 33 | } 34 | 35 | function values(Set storage set) internal view returns (bytes[] memory) { 36 | return set._values; 37 | } 38 | } 39 | 40 | contract Storage4 { 41 | using EnumerableSet for Set; 42 | 43 | mapping(uint256 => Set) internal map; 44 | 45 | function add(uint256 key, bytes calldata value) external { 46 | map[key].add(value); 47 | } 48 | 49 | function lookup(uint256 key) internal view returns (Set storage) { 50 | return map[key]; 51 | } 52 | 53 | function totalValues(uint256 key) public view virtual returns (uint256) { 54 | return lookup(key).length(); 55 | } 56 | 57 | function valueAt(uint256 key, uint256 index) external view returns (bytes memory) { 58 | return lookup(key).at(index); 59 | } 60 | 61 | function valuesOf(uint256 key) external view returns (bytes[] memory) { 62 | return lookup(key).values(); 63 | } 64 | } 65 | 66 | 67 | contract Storage4Test is SymTest, Test { 68 | Storage4 s; 69 | 70 | function setUp() public { 71 | s = new Storage4(); 72 | } 73 | 74 | function check_add_1(uint k) public { 75 | bytes[] memory v = new bytes[](10); 76 | 77 | v[1] = svm.createBytes(31, "v1"); 78 | v[2] = svm.createBytes(32, "v2"); 79 | v[3] = svm.createBytes(33, "v3"); 80 | 81 | s.add(k, v[1]); 82 | s.add(k, v[2]); 83 | s.add(k, v[3]); 84 | 85 | assert(keccak256(s.valueAt(k, 0)) == keccak256(v[1])); 86 | assert(keccak256(s.valueAt(k, 1)) == keccak256(v[2])); 87 | assert(keccak256(s.valueAt(k, 2)) == keccak256(v[3])); 88 | 89 | assert(s.totalValues(k) == 3); 90 | } 91 | 92 | function check_add_2(uint k) public { 93 | bytes[] memory v = new bytes[](10); 94 | 95 | // note: v1 and v2 may be equal, since they are of the same size 96 | v[1] = svm.createBytes(32, "v1"); 97 | v[2] = svm.createBytes(32, "v2"); 98 | 99 | s.add(k, v[1]); 100 | s.add(k, v[2]); 101 | 102 | if (s.totalValues(k) == 2) { 103 | assert(keccak256(s.valueAt(k, 0)) == keccak256(v[1])); 104 | assert(keccak256(s.valueAt(k, 1)) == keccak256(v[2])); 105 | } else { 106 | assert(s.totalValues(k) == 1); 107 | 108 | assert(keccak256(s.valueAt(k, 0)) == keccak256(v[1])); 109 | assert(keccak256(s.valueAt(k, 0)) == keccak256(v[2])); 110 | 111 | assert(keccak256(v[1]) == keccak256(v[2])); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/regression/test/StorageSlot.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 6 | 7 | contract StorageSlotTest { 8 | mapping (uint => uint) map; 9 | 10 | // keccak256(abi.encode(1, 0)) 11 | bytes32 constant slot_map_one = bytes32(0xada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7d); 12 | 13 | // keccak256(abi.encode(2, 0)) 14 | bytes32 constant slot_map_two = bytes32(0xabbb5caa7dda850e60932de0934eb1f9d0f59695050f761dc64e443e5030a569); 15 | 16 | // not annotated as constant to avoid constant propagation optimization 17 | uint one = 1; 18 | uint two = 2; 19 | 20 | function check_setup() public { 21 | assert(slot_map_one == keccak256(abi.encode(1, 0))); 22 | } 23 | 24 | function check_keccak_slot_1_pass(uint value) public { 25 | // note: compiler doesn't optimize because global variable is used as key 26 | map[one] = value; // sstore with keccak expression 27 | 28 | assert(sload(slot_map_one) == value); // sload with precomputed hash 29 | assert(map[one] == value); // sload with keccak expression 30 | } 31 | 32 | // this test passes because halmos internally precomputes the slots for m[0] and m[1], for any m where slot(m) < 256. 33 | // in general, however, directly initializing storage with a precomputed hash is not supported by halmos. see the test below. 34 | function check_keccak_slot_2_pass(uint value) public { 35 | sstore(slot_map_one, value); // sstore with precomputed hash 36 | 37 | assert(sload(slot_map_one) == value); // sload with precomputed hash 38 | assert(map[one] == value); // sload with keccak expression 39 | } 40 | 41 | // this test failed due to m[2] beyond the scope of halmos internal precomputation. see the above test for comparison. 42 | function check_keccak_slot_2_fail(uint value) public { 43 | sstore(slot_map_two, value); // sstore with precomputed hash 44 | 45 | assert(sload(slot_map_two) == value); // sload with precomputed hash 46 | assert(map[two] == value); // sload with keccak expression 47 | } 48 | 49 | function check_keccak_slot_3_pass(uint value) public { 50 | // note: compiler may or may not optimize depending on compiler version or other optimization configuration 51 | map[1] = value; // may be optimized to sstore with precomputed hash 52 | 53 | assert(sload(slot_map_one) == value); // sload with precomputed hash 54 | assert(map[one] == value); // sload with keccak expression 55 | } 56 | 57 | function sload(bytes32 slot) internal returns (uint value) { 58 | assembly { 59 | value := sload(slot) 60 | } 61 | } 62 | 63 | function sstore(bytes32 slot, uint value) internal { 64 | assembly { 65 | sstore(slot, value) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/regression/test/Store.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract C { 7 | uint public x; 8 | mapping (uint => uint) public m; 9 | uint[] public a; 10 | } 11 | 12 | contract StoreTest is Test { 13 | C c; 14 | 15 | function setUp() public { 16 | c = new C(); 17 | } 18 | 19 | // TODO: support symbolic base slot 20 | // function check_store(bytes32 key, bytes32 value) public { 21 | // vm.store(address(c), key, value); 22 | // assert(vm.load(address(c), key) == value); 23 | // } 24 | 25 | // TODO: support uninitialized accounts 26 | // function check_store_Uninit(bytes32 value) public { 27 | // vm.store(address(0), 0, value); 28 | // assert(vm.load(address(0), 0) == value); 29 | // } 30 | 31 | function check_store_Scalar(uint value) public { 32 | vm.store(address(c), 0, bytes32(value)); 33 | assert(c.x() == value); 34 | assert(uint(vm.load(address(c), 0)) == value); 35 | } 36 | 37 | function check_store_Mapping(uint key, uint value) public { 38 | vm.store(address(c), keccak256(abi.encode(key, 1)), bytes32(value)); 39 | assert(c.m(key) == value); 40 | assert(uint(vm.load(address(c), keccak256(abi.encode(key, 1)))) == value); 41 | } 42 | 43 | function check_store_Array(uint key, uint value) public { 44 | vm.assume(key < 2**32); // to avoid overflow 45 | vm.store(address(c), bytes32(uint(2)), bytes32(uint(1) + key)); 46 | vm.store(address(c), bytes32(uint(keccak256(abi.encode(2))) + key), bytes32(value)); 47 | assert(c.a(key) == value); 48 | assert(uint(vm.load(address(c), bytes32(uint(keccak256(abi.encode(2))) + key))) == value); 49 | } 50 | 51 | function check_store_nonexistent_account() public { 52 | // vm.store() is not allowed for a nonexistent account 53 | vm.store(address(0xdeadbeef), bytes32(0), bytes32(0)); // HalmosException 54 | } 55 | 56 | function check_load_nonexistent_account(uint slot) public { 57 | // vm.load() from nonexistent accounts returns zero 58 | assertEq(vm.load(address(0xdeadbeef), bytes32(slot)), 0); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/regression/test/SymbolicCall.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract C { 7 | function foo() public pure returns (bool) { 8 | return true; 9 | } 10 | } 11 | 12 | contract SymbolicCallTest is Test { 13 | C c1; 14 | C c2; 15 | 16 | function setUp() public { 17 | c1 = new C(); 18 | c2 = new C(); 19 | } 20 | 21 | function check_foo(address addr) public { 22 | (bool success,) = addr.call(abi.encodeWithSelector(C.foo.selector)); 23 | // this will fail if addr could be aliased to the test contract 24 | assertTrue(success); 25 | } 26 | 27 | function foo() public pure returns (bool) { 28 | revert(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/regression/test/TStore.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract C { 7 | uint public slot0; 8 | 9 | function sstore(uint slot, uint value) public { 10 | assembly { 11 | sstore(slot, value) 12 | } 13 | } 14 | 15 | function sload(uint slot) public returns (uint value) { 16 | assembly { 17 | value := sload(slot) 18 | } 19 | } 20 | 21 | function tstore(uint slot, uint value) public { 22 | assembly { 23 | tstore(slot, value) 24 | } 25 | } 26 | 27 | function tload(uint slot) public returns (uint value) { 28 | assembly { 29 | value := tload(slot) 30 | } 31 | } 32 | } 33 | 34 | /// @custom:halmos --storage-layout generic 35 | contract TStoreTest is Test { 36 | C c; 37 | 38 | function setUp() public { 39 | c = new C(); 40 | } 41 | 42 | function check_sload() public { 43 | assertEq(c.sload(0), 0); 44 | } 45 | 46 | function check_tload() public { 47 | assertEq(c.tload(0), 0); 48 | } 49 | 50 | function check_sstore(uint x) public { 51 | c.sstore(0, x); 52 | assertEq(c.sload(0), x); 53 | assertEq(c.slot0(), x); 54 | 55 | // transient storage isn't affected 56 | assertEq(c.tload(0), 0); 57 | } 58 | 59 | function check_tstore(uint x) public { 60 | c.tstore(0, x); 61 | assertEq(c.tload(0), x); 62 | 63 | // persistent storage isn't affected 64 | assertEq(c.slot0(), 0); 65 | assertEq(c.sload(0), 0); 66 | } 67 | 68 | function invariant_storage() public { 69 | assertEq(c.sload(0), 0); // fail 70 | } 71 | 72 | function invariant_transient_storage() public { 73 | // note: transient storage is reset after each tx in the invariant tx sequence 74 | assertEq(c.tload(0), 0); // pass 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/regression/test/TestConstructor.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract TestConstructorTest { 5 | uint initialized = 1; 6 | uint constant const = 2; 7 | uint immutable flag; 8 | uint value; 9 | uint codesize_; 10 | uint extcodesize_; 11 | 12 | constructor () { 13 | flag = 3; 14 | value = 4; 15 | 16 | assembly { 17 | sstore(codesize_.slot, codesize()) 18 | sstore(extcodesize_.slot, extcodesize(address())) 19 | } 20 | } 21 | 22 | function check_value() public view { 23 | assert(initialized == 1); 24 | assert(const == 2); 25 | assert(flag == 3); 26 | assert(value == 4); 27 | 28 | assert(codesize_ > 0); 29 | assert(extcodesize_ == 0); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/regression/test/Token.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import {SymTest} from "halmos-cheatcodes/SymTest.sol"; 5 | import {Test} from "forge-std/Test.sol"; 6 | 7 | /// @custom:halmos --solver-timeout-assertion 0 8 | contract TokenTest is SymTest, Test { 9 | Token token; 10 | 11 | function setUp() public { 12 | token = new Token(); 13 | 14 | // set the balances of three arbitrary accounts to arbitrary symbolic values 15 | for (uint i = 0; i < 3; i++) { 16 | address receiver = svm.createAddress('receiver'); 17 | uint256 amount = svm.createUint256('amount'); 18 | token.transfer(receiver, amount); 19 | } 20 | } 21 | 22 | function check_BalanceInvariant() public { 23 | // consider two arbitrary distinct accounts 24 | address caller = svm.createAddress('caller'); 25 | address others = svm.createAddress('others'); 26 | vm.assume(others != caller); 27 | 28 | // record their current balances 29 | uint256 oldBalanceCaller = token.balanceOf(caller); 30 | uint256 oldBalanceOthers = token.balanceOf(others); 31 | 32 | // consider an arbitrary function call to the token from the caller 33 | vm.prank(caller); 34 | bytes memory data = svm.createBytes(100, 'data'); 35 | (bool success,) = address(token).call(data); 36 | vm.assume(success); 37 | 38 | // ensure that the caller cannot spend others' tokens 39 | assert(token.balanceOf(caller) <= oldBalanceCaller); 40 | assert(token.balanceOf(others) >= oldBalanceOthers); 41 | } 42 | } 43 | 44 | contract Token { 45 | mapping(address => uint) public balanceOf; 46 | 47 | constructor() { 48 | balanceOf[msg.sender] = 1e27; 49 | } 50 | 51 | function transfer(address to, uint amount) public { 52 | _transfer(msg.sender, to, amount); 53 | } 54 | 55 | function _transfer(address from, address to, uint amount) public { 56 | balanceOf[from] -= amount; 57 | balanceOf[to] += amount; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/regression/test/UnknownCall.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | import {IERC721TokenReceiver} from "forge-std/interfaces/IERC721.sol"; 7 | 8 | contract UnknownCallTest is Test { 9 | function check_unknown_retsize_0(address addr) public { 10 | (bool success, bytes memory retdata) = addr.call(abi.encodeWithSelector(IERC721TokenReceiver.onERC721Received.selector, address(0), address(0), 0, "")); 11 | if (success) { 12 | // if addr is not this, then addr is nonexisting thus the call to addr will immediately succeed with empty returndata 13 | assertNotEq(addr, address(this)); 14 | assertEq(retdata.length, 0); 15 | } else { 16 | // if addr is equal to this, then the call to onERC721Received will fail since it is not implemented in this contract 17 | assertEq(addr, address(this)); 18 | assertEq(retdata.length, 0); // the default fallback reverts with empty returndata 19 | } 20 | } 21 | 22 | function check_unknown_call(address addr, uint amount, uint initial) public { 23 | vm.assume(addr != address(this)); 24 | vm.deal(addr, 0); 25 | vm.deal(address(this), initial); 26 | 27 | (bool success, bytes memory retdata) = payable(addr).call{ value: amount }(""); 28 | 29 | assert(retdata.length == 0); // the returndata of a nonexisting contract call is always empty, even if it fails due to insufficient balance 30 | 31 | if (success) { 32 | assert(initial >= amount); 33 | assert(addr.balance == amount); 34 | assert(address(this).balance == initial - amount); 35 | } else { 36 | assert(addr.balance == 0); 37 | assert(address(this).balance == initial); 38 | } 39 | } 40 | 41 | function check_unknown_send(address addr, uint amount, uint initial) public { 42 | vm.assume(addr != address(this)); 43 | vm.deal(addr, 0); 44 | vm.deal(address(this), initial); 45 | 46 | bool success = payable(addr).send(amount); 47 | 48 | if (success) { 49 | assert(initial >= amount); 50 | assert(addr.balance == amount); 51 | assert(address(this).balance == initial - amount); 52 | } else { 53 | assert(initial < amount); 54 | assert(addr.balance == 0); 55 | assert(address(this).balance == initial); 56 | } 57 | } 58 | 59 | function check_unknown_send_fail(address addr) public { 60 | vm.assume(addr != address(this)); 61 | vm.deal(addr, 0); 62 | vm.deal(address(this), 1); 63 | 64 | bool success = payable(addr).send(2); // get stuck 65 | 66 | assert(!success); 67 | } 68 | 69 | function check_unknown_transfer(address addr, uint amount, uint initial) public { 70 | vm.assume(addr != address(this)); 71 | vm.deal(addr, 0); 72 | vm.deal(address(this), initial); 73 | 74 | payable(addr).transfer(amount); // revert if fail 75 | 76 | // at this point, transfer succeeds because it reverts on failure 77 | assert(initial >= amount); 78 | assert(addr.balance == amount); 79 | assert(address(this).balance == initial - amount); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/regression/test/UnsupportedOpcode.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | // no hop 5 | contract X { 6 | function foo() internal { 7 | assembly { 8 | selfdestruct(0) // unsupported opcode 9 | } 10 | } 11 | 12 | function check_unsupported_opcode() public { 13 | foo(); // unsupported error 14 | } 15 | } 16 | 17 | // 1 hop 18 | contract Y { 19 | X x; 20 | 21 | function setUp() public { 22 | x = new X(); 23 | } 24 | 25 | function check_unsupported_opcode() public { 26 | x.check_unsupported_opcode(); // unsupported error 27 | } 28 | } 29 | 30 | // 2 hops 31 | contract Z { 32 | Y y; 33 | 34 | function setUp() public { 35 | y = new Y(); 36 | y.setUp(); 37 | } 38 | 39 | function check_unsupported_opcode() public { 40 | y.check_unsupported_opcode(); // unsupported error 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/regression/test/Warp.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | contract C is Test { 7 | constructor(uint time) { 8 | vm.warp(time); 9 | } 10 | } 11 | 12 | contract Ext is Test { 13 | function warp(uint time) public { 14 | vm.warp(time); 15 | } 16 | } 17 | 18 | contract WarpTest is Test { 19 | Ext ext; 20 | C c; 21 | 22 | function setUp() public { 23 | ext = new Ext(); 24 | vm.warp(1000); 25 | } 26 | 27 | function warp(uint time) public { 28 | vm.warp(time); 29 | } 30 | 31 | function check_warp(uint time) public { 32 | vm.warp(time); 33 | assert(block.timestamp == time); 34 | } 35 | 36 | function check_warp_Internal(uint time) public { 37 | warp(time); 38 | assert(block.timestamp == time); 39 | } 40 | 41 | function check_warp_External(uint time) public { 42 | ext.warp(time); 43 | assert(block.timestamp == time); 44 | } 45 | 46 | function check_warp_ExternalSelf(uint time) public { 47 | this.warp(time); 48 | assert(block.timestamp == time); 49 | } 50 | 51 | function check_warp_New(uint time) public { 52 | c = new C(time); 53 | assert(block.timestamp == time); 54 | } 55 | 56 | function check_warp_Reset(uint time1, uint time2) public { 57 | vm.warp(time1); 58 | assert(block.timestamp == time1); 59 | vm.warp(time2); 60 | assert(block.timestamp == time2); 61 | } 62 | 63 | function check_warp_SetUp() public view { 64 | assert(block.timestamp == 1000); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/solver/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['../lib', 'lib'] 5 | 6 | evm_version = 'cancun' 7 | 8 | force = false 9 | # compile options used by halmos (to prevent unnecessary recompilation when running forge test and halmos together) 10 | extra_output = ["storageLayout", "metadata"] 11 | -------------------------------------------------------------------------------- /tests/solver/remappings.txt: -------------------------------------------------------------------------------- 1 | openzeppelin/=../lib/openzeppelin-contracts/contracts/ 2 | ds-test/=../lib/forge-std/lib/ds-test/src/ 3 | -------------------------------------------------------------------------------- /tests/solver/test/Math.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract MathTest { 5 | function check_Avg(uint a, uint b) public pure { 6 | unchecked { 7 | uint r1 = (a & b) + (a ^ b) / 2; 8 | uint r2 = (a + b) / 2; 9 | assert(r1 == r2); 10 | } 11 | } 12 | 13 | /// @custom:halmos --solver-timeout-assertion 10000 14 | function check_deposit(uint a, uint A1, uint S1) public pure { 15 | uint s = (a * S1) / A1; 16 | 17 | uint A2 = A1 + a; 18 | uint S2 = S1 + s; 19 | 20 | // (A1 / S1 <= A2 / S2) 21 | assert(A1 * S2 <= A2 * S1); // no counterexample 22 | } 23 | 24 | /// @custom:halmos --solver-timeout-assertion 0 25 | function check_mint(uint s, uint A1, uint S1) public pure { 26 | uint a = (s * A1) / S1; 27 | 28 | uint A2 = A1 + a; 29 | uint S2 = S1 + s; 30 | 31 | // (A1 / S1 <= A2 / S2) 32 | assert(A1 * S2 <= A2 * S1); // counterexamples exist 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/solver/test/Solver.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | // from https://github.com/a16z/halmos/issues/57 5 | 6 | /// @custom:halmos --solver-timeout-assertion 10000 7 | contract SolverTest { 8 | 9 | function foo(uint x) public pure returns (uint) { 10 | if(x < type(uint128).max) 11 | return x * 42; 12 | else return x; 13 | } 14 | 15 | function check_foo(uint a, uint b) public pure { 16 | if(b > a) { 17 | assert(foo(b) > foo(a)); 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /tests/test_fixtures.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from halmos.__main__ import mk_solver 4 | from halmos.calldata import FunctionInfo 5 | from halmos.config import default_config 6 | from halmos.sevm import SEVM 7 | 8 | 9 | @pytest.fixture 10 | def args(): 11 | return default_config() 12 | 13 | 14 | @pytest.fixture 15 | def fun_info(): 16 | return FunctionInfo("TestContract", "test", "test()", "f8a8fd6d") 17 | 18 | 19 | @pytest.fixture 20 | def sevm(args, fun_info): 21 | return SEVM(args, fun_info) 22 | 23 | 24 | @pytest.fixture 25 | def solver(args): 26 | return mk_solver(args) 27 | 28 | 29 | @pytest.fixture 30 | def halmos_options(request): 31 | return request.config.getoption("--halmos-options") 32 | -------------------------------------------------------------------------------- /tests/test_halmos.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | import json 3 | 4 | import pytest 5 | 6 | from halmos.__main__ import _main 7 | from halmos.bytevec import ByteVec 8 | from halmos.sevm import con 9 | from halmos.traces import rendered_calldata 10 | 11 | 12 | @pytest.mark.parametrize( 13 | "cmd, expected_path", 14 | [ 15 | ( 16 | ["--root", "tests/regression"], 17 | "tests/expected/all.json", 18 | ), 19 | ( 20 | ["--root", "tests/ffi"], 21 | "tests/expected/ffi.json", 22 | ), 23 | ( 24 | ["--root", "tests/solver"], 25 | "tests/expected/solver.json", 26 | ), 27 | ( 28 | ["--root", "examples/simple"], 29 | "tests/expected/simple.json", 30 | ), 31 | ( 32 | ["--root", "examples/tokens/ERC20"], 33 | "tests/expected/erc20.json", 34 | ), 35 | ( 36 | ["--root", "examples/tokens/ERC721"], 37 | "tests/expected/erc721.json", 38 | ), 39 | ], 40 | ids=( 41 | "tests/regression", 42 | "tests/ffi", 43 | "long:tests/solver", 44 | "long:examples/simple", 45 | "long:examples/tokens/ERC20", 46 | "long:examples/tokens/ERC721", 47 | ), 48 | ) 49 | def test_main(cmd, expected_path, halmos_options): 50 | actual = dataclasses.asdict(_main(cmd + halmos_options.split())) 51 | with open(expected_path, encoding="utf8") as f: 52 | expected = json.load(f) 53 | assert expected["exitcode"] == actual["exitcode"] 54 | assert_eq(expected["test_results"], actual["test_results"]) 55 | 56 | 57 | @pytest.mark.parametrize( 58 | "cmd", 59 | [ 60 | ["--root", "tests/regression", "--contract", "SetupFailTest"], 61 | ], 62 | ids=("SetupFailTest",), 63 | ) 64 | def test_main_fail(cmd, halmos_options): 65 | actual = dataclasses.asdict(_main(cmd + halmos_options.split())) 66 | assert actual["exitcode"] != 0 67 | 68 | 69 | def assert_eq(m1: dict, m2: dict) -> int: 70 | assert list(m1.keys()) == list(m2.keys()) 71 | for c in m1: 72 | l1 = sorted(m1[c], key=lambda x: x["name"]) 73 | l2 = sorted(m2[c], key=lambda x: x["name"]) 74 | assert len(l1) == len(l2), c 75 | for r1, r2 in zip(l1, l2, strict=False): 76 | assert r1["name"] == r2["name"] 77 | assert r1["exitcode"] == r2["exitcode"], f"{c} {r1['name']}" 78 | assert r1["num_models"] == r2["num_models"], f"{c} {r1['name']}" 79 | 80 | 81 | def test_rendered_calldata_symbolic(): 82 | assert rendered_calldata(ByteVec([con(1, 8), con(2, 8), con(3, 8)])) == "0x010203" 83 | 84 | 85 | def test_rendered_calldata_symbolic_singleton(): 86 | assert rendered_calldata(ByteVec(con(0x42, 8))) == "0x42" 87 | 88 | 89 | def test_rendered_calldata_concrete(): 90 | assert rendered_calldata(ByteVec([1, 2, 3])) == "0x010203" 91 | 92 | 93 | def test_rendered_calldata_mixed(): 94 | assert rendered_calldata(ByteVec([con(1, 8), 2, con(3, 8)])) == "0x010203" 95 | 96 | 97 | def test_rendered_calldata_empty(): 98 | assert rendered_calldata(ByteVec()) == "0x" 99 | -------------------------------------------------------------------------------- /tests/test_solve.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from halmos.solve import ModelVariable, parse_model_str 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "full_name", 8 | [ 9 | "halmos_y_uint256_043cfd7_01", 10 | "p_y_uint256_043cfd7_01", 11 | ], 12 | ) 13 | def test_smtlib_z3_bv_output(full_name): 14 | smtlib_str = f""" 15 | (define-fun {full_name} () (_ BitVec 256) 16 | #x0000000000000000000000000000000000000000000000000000000000000000) 17 | """ 18 | model = parse_model_str(smtlib_str) 19 | 20 | assert model[full_name] == ModelVariable( 21 | full_name=full_name, 22 | variable_name="y", 23 | solidity_type="uint256", 24 | smt_type="BitVec 256", 25 | size_bits=256, 26 | value=0, 27 | ) 28 | 29 | 30 | # note that yices only produces output like this with --smt2-model-format 31 | # otherwise we get something like (= x #b00000100) 32 | @pytest.mark.parametrize( 33 | "full_name", 34 | [ 35 | "halmos_z_uint256_cabf047_02", 36 | "p_z_uint256_cabf047_02", 37 | ], 38 | ) 39 | def test_smtlib_yices_binary_output(full_name): 40 | smtlib_str = f""" 41 | (define-fun 42 | {full_name} 43 | () 44 | (_ BitVec 256) 45 | #b1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) 46 | """ 47 | model = parse_model_str(smtlib_str) 48 | assert model[full_name] == ModelVariable( 49 | full_name=full_name, 50 | variable_name="z", 51 | solidity_type="uint256", 52 | smt_type="BitVec 256", 53 | size_bits=256, 54 | value=1 << 255, 55 | ) 56 | 57 | 58 | @pytest.mark.parametrize( 59 | "full_name", 60 | [ 61 | "halmos_z_uint256_11ce021_08", 62 | "p_z_uint256_11ce021_08", 63 | ], 64 | ) 65 | def test_smtlib_yices_decimal_output(full_name): 66 | val = 57896044618658097711785492504343953926634992332820282019728792003956564819968 67 | smtlib_str = f""" 68 | (define-fun {full_name} () (_ BitVec 256) (_ bv{val} 256)) 69 | """ 70 | model = parse_model_str(smtlib_str) 71 | assert model[full_name] == ModelVariable( 72 | full_name=full_name, 73 | variable_name="z", 74 | solidity_type="uint256", 75 | smt_type="BitVec 256", 76 | size_bits=256, 77 | value=val, 78 | ) 79 | 80 | 81 | @pytest.mark.parametrize( 82 | "full_name", 83 | [ 84 | "halmos_x_uint8_043cfd7_01", 85 | "p_x_uint8_043cfd7_01", 86 | ], 87 | ) 88 | def test_smtlib_stp_output(full_name): 89 | # we should tolerate: 90 | # - the extra (model) command 91 | # - duplicate variable names 92 | # - the initial `sat` result 93 | # - the `|` around the variable name 94 | # - the space in `( define-fun ...)` 95 | smtlib_str = f""" 96 | sat 97 | (model 98 | ( define-fun |{full_name}| () (_ BitVec 8) #x04 ) 99 | ) 100 | (model 101 | ( define-fun |{full_name}| () (_ BitVec 8) #x04 ) 102 | ) 103 | """ 104 | model = parse_model_str(smtlib_str) 105 | assert model[full_name] == ModelVariable( 106 | full_name=full_name, 107 | variable_name="x", 108 | solidity_type="uint8", 109 | smt_type="BitVec 8", 110 | size_bits=8, 111 | value=4, 112 | ) 113 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from z3 import ( 2 | ULE, 3 | BitVec, 4 | BitVecSort, 5 | BitVecVal, 6 | Function, 7 | Not, 8 | simplify, 9 | ) 10 | 11 | from halmos.utils import ( 12 | f_sha3_256, 13 | match_dynamic_array_overflow_condition, 14 | ) 15 | 16 | 17 | def test_match_dynamic_array_overflow_condition(): 18 | # Create Z3 objects 19 | slot = BitVec("slot", 256) 20 | offset = BitVecVal(1000, 256) # Less than 2**64 21 | 22 | # Test the function 23 | cond = Not(ULE(f_sha3_256(slot), offset + f_sha3_256(slot))) 24 | assert match_dynamic_array_overflow_condition(cond) 25 | 26 | # Test with opposite order of addition 27 | opposite_order_cond = Not(ULE(f_sha3_256(slot), f_sha3_256(slot) + offset)) 28 | assert not match_dynamic_array_overflow_condition(opposite_order_cond) 29 | 30 | # Test with opposite order after simplification 31 | simplified_opposite_order_cond = simplify( 32 | Not(ULE(f_sha3_256(slot), f_sha3_256(slot) + offset)) 33 | ) 34 | assert match_dynamic_array_overflow_condition(simplified_opposite_order_cond) 35 | 36 | # Test with offset = 2**64 - 1 (should match) 37 | max_valid_offset = BitVecVal(2**64 - 1, 256) 38 | max_valid_cond = Not(ULE(f_sha3_256(slot), max_valid_offset + f_sha3_256(slot))) 39 | assert match_dynamic_array_overflow_condition(max_valid_cond) 40 | 41 | # Test with offset >= 2**64 42 | large_offset = BitVecVal(2**64, 256) 43 | large_offset_cond = Not(ULE(f_sha3_256(slot), large_offset + f_sha3_256(slot))) 44 | assert not match_dynamic_array_overflow_condition(large_offset_cond) 45 | 46 | # Test with a different function 47 | different_func = Function("different_func", BitVecSort(256), BitVecSort(256)) 48 | non_matching_cond = Not(ULE(different_func(slot), offset + different_func(slot))) 49 | assert not match_dynamic_array_overflow_condition(non_matching_cond) 50 | 51 | # Test with just ULE, not Not(ULE(...)) 52 | ule_only = ULE(f_sha3_256(slot), offset + f_sha3_256(slot)) 53 | assert not match_dynamic_array_overflow_condition(ule_only) 54 | 55 | # Test with mismatched slots 56 | slot2 = BitVec("slot2", 256) 57 | mismatched_slots = Not(ULE(f_sha3_256(slot), offset + f_sha3_256(slot2))) 58 | assert not match_dynamic_array_overflow_condition(mismatched_slots) 59 | --------------------------------------------------------------------------------