├── .envrc ├── .gitignore ├── .gitmodules ├── Dockerfile ├── README.md ├── bench.py ├── check_results.py ├── clean_table.sql ├── clear-results.sh ├── create_table.sql ├── flake.lock ├── flake.nix ├── foundry.toml ├── gen_graphs.py ├── src ├── common │ ├── auth.sol │ └── erc20.sol ├── safe │ ├── 1tx-abstract │ │ ├── erc721A.sol │ │ └── sym-storage-safe.sol │ └── ds-test │ │ ├── amm.sol │ │ ├── arith-safe.sol │ │ ├── assert-true.sol │ │ ├── bitwise-safe.sol │ │ ├── calldata-safe.sol │ │ ├── constructors.sol │ │ ├── deposit.sol │ │ ├── erc20.sol │ │ ├── keccak.sol │ │ ├── loops-safe.sol │ │ ├── memory-safe.sol │ │ └── storage-safe.sol └── unsafe │ ├── 1tx-abstract │ └── sym-storage-unsafe.sol │ └── ds-test │ ├── arith-unsafe.sol │ ├── assert-false.sol │ ├── bad-vault.sol │ ├── branch-magic.sol │ ├── calldata-unsafe.sol │ ├── loops-unsafe.sol │ ├── memory-unsafe.sol │ ├── minivat.sol │ ├── sacred-geometry.sol │ ├── storage-unsafe.sol │ ├── synthetic-manybranch.sol │ ├── withdraw.sol │ └── xor-magic.sol └── tools ├── halmos.sh ├── halmos_version.sh ├── hevm.sh ├── hevm_version.sh ├── kevm.sh ├── kontrol.sh ├── kontrol_version.sh ├── test-kevm.sh └── utils.sh /.envrc: -------------------------------------------------------------------------------- 1 | use_flake 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .direnv/ 2 | **/.k-artifacts/ 3 | cache/ 4 | out/ 5 | *.json 6 | *.csv 7 | tags 8 | *.eps 9 | *.db 10 | graphs/ 11 | *.smt2 12 | *.expr 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/ERC721A"] 5 | path = lib/ERC721A 6 | url = https://github.com/d-xo/ERC721A 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | LABEL maintainer="mate.soos@ethereum.org" 4 | LABEL version="0.1" 5 | LABEL description="This is a custom Docker Image for symbolic execution benchmark running" 6 | 7 | # RUN nix-env -iA nixpkgs.su 8 | RUN DEBIAN_FRONTEND=noninteractive apt-get update 9 | RUN DEBIAN_FRONTEND=noninteractive apt-get install -y curl xz-utils sudo 10 | RUN DEBIAN_FRONTEND=noninteractive apt-get install -y git bash 11 | 12 | RUN adduser --system --group bench 13 | RUN usermod -aG sudo bench 14 | RUN echo "bench ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 15 | # RUN useradd -ms /bin/sh bench 16 | RUN usermod --shell /bin/bash bench 17 | USER bench 18 | WORKDIR /home/bench 19 | 20 | SHELL ["/bin/bash", "-c"] 21 | ENV USER=bench 22 | ENV HOME=/home/bench 23 | 24 | RUN sudo install -d -m755 -o $(id -u) -g $(id -g) /nix 25 | RUN curl -L https://nixos.org/nix/install | sh 26 | RUN source $HOME/.nix-profile/etc/profile.d/nix.sh && nix-shell -p cachix --command "cachix use k-framework" 27 | 28 | RUN git clone https://github.com/eth-sc-comp/benchmarks 29 | WORKDIR /home/bench/benchmarks 30 | 31 | RUN source $HOME/.nix-profile/etc/profile.d/nix.sh && nix --extra-experimental-features flakes --extra-experimental-features nix-command develop 32 | 33 | RUN export HOME=/home/bench USER=bench 34 | ENTRYPOINT ["/bin/bash", "-c", "source $HOME/.nix-profile/etc/profile.d/nix.sh && nix --extra-experimental-features flakes --extra-experimental-features nix-command develop"] 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ethereum Smart Contract Analysis Benchmarking 2 | 3 | This repository contains a set of benchmarks, a bench harness, and graph 4 | generation utilities that are intended to provide some kind of objective 5 | measurements for the strengths and weaknesses of various analysis 6 | tooling targeting Ethereum smart contracts. In practice this means tools that 7 | consume Solidity, Yul, or EVM bytecode. 8 | 9 | The benchmarks in this repo should be useful to developers of all kinds of 10 | tools, including fuzzers, static analyzers, and symbolic execution engines. 11 | 12 | ### Quick Start Guide -- Linux 13 | 14 | Install nix (see [here](https://nixos.org/download.html)). Then: 15 | 16 | ``` 17 | # optional, but will make subsequent steps significantly faster 18 | nix-shell -p cachix --command "cachix use k-framework" 19 | 20 | nix develop # this may take some time 21 | ./bench.py 22 | ./gen_graphs.py 23 | cd graphs 24 | ``` 25 | 26 | You can look at the graphs under the folder `graphs` 27 | 28 | ### Quick Start Guide -- Mac 29 | 30 | You will need to create a docker image. This is because unfortunately MacOS 31 | does not support the procfs (i.e. `/proc`) and `runlim` does not work with 32 | `sysctl`. We suggest the following setup: 33 | 34 | ``` 35 | brew install colima 36 | colima start 37 | docker ps -a 38 | ``` 39 | 40 | If `docker ps -a` ran fine, then you can now create a docker image via: 41 | 42 | ``` 43 | docker build --tag sym-bench . 44 | docker run -it --rm sym-bench 45 | ./bench.py 46 | ./gen_graphs.py 47 | ``` 48 | 49 | ## Using This Repository 50 | 51 | We use Nix to provide a zero overhead reproducible environment that contains 52 | all tools required to run the benchmarks. If you want to add a new tool then 53 | you need to extend the `flake.nix` so that this tool is present in the 54 | `devShell`. 55 | 56 | To enter the environment, run `nix develop`. Once you have a working shell, you 57 | can run `python bench.py` to execute the benchmarks. The results are collected 58 | in `results.db` sqlite3 database and the csv and json files 59 | `results-[timestamp].csv/json`. You can view these files using standard tools 60 | such as libreoffice, Excel, jq, etc. 61 | 62 | To generate graphs, run `python gen_graph.py`. Then, you can 63 | look at the cumulative distribution function (CDF) graph to get an overview. 64 | Here, the different tools' performances are displayed, with X axis showing 65 | time, and the Y axis showing the number of problems solved within that time 66 | frame. Typically, a tool is be better when it solves more instances (i.e. 67 | higher on the Y axis) while being faster (i.e. more to the left on the X axis) 68 | 69 | The system also generates one-on-one comparisons for all tested tools, and 70 | a box chart of all tools' performance on all instances. 71 | 72 | ## Adding a New Benchmark 73 | 74 | First, a note on benchmark selection. It is important to keep in mind that the 75 | set of benchmarks the tools are evaluated on significantly impacts which tool 76 | "looks" best on e.g. the CDF plot. For fairness, we strongly recommend contract 77 | authors to add interesting problems via pull requests. A problem can be 78 | interesting because e.g. it's often needed but generally slow to solve, or 79 | because some or even all tools could not solve it. This can help drive 80 | development of tools and ensure more fairness in the comparisons. 81 | 82 | There are two types of benchmarks. The ones under `src/safe/1tx-abstract` and 83 | under `src/unsafe/1tx-abstract` are standard Solidity contracts that have all 84 | their functions checked to have triggerable assert statements. For these files, 85 | either the entire contract is deemed safe or unsafe. The files under 86 | `src/safe/ds-test` and under `src/unsafe/ds-test` are tested differently. Here, 87 | only functions starting with the `prove` keyword are tested, individually, 88 | for safety. Hence, each function may be individually deemed safe/unsafe. Contracts 89 | under these directories can use the full set of foundry 90 | [cheatcodes](https://book.getfoundry.sh/cheatcodes/) and assertion helpers. 91 | 92 | An example `1tx` benchmark is below. It would be under 93 | `src/unsafe/1tx-abstract` since the `assert` can be triggered with `x=10`. 94 | 95 | ```sol 96 | contract C { 97 | function f(uint256 x) public { 98 | assert(x != 10); 99 | } 100 | } 101 | ``` 102 | 103 | 104 | An example `ds-test` benchmark is below. It would be under 105 | `src/unsafe/ds-test` since the `assert` can be triggered with `x=11`. 106 | 107 | ```sol 108 | contract C { 109 | function prove_f(uint256 x) public { 110 | assert(x != 11); 111 | } 112 | } 113 | ``` 114 | 115 | ## Execution Environments 116 | 117 | Currently, there is a global 25 second wall clock timeout applied to all tool 118 | invocations. This is adjustable with the `-t` option to `bench.py`. Tools that 119 | take longer than this to produce a result for a benchmark will have an 120 | "unknown" result assigned. There is currently no memory limit enforced. 121 | 122 | Each tool is allowed to use as many threads as it wishes, typically 123 | auto-detected by each tool to be the number of cores in the system. This means 124 | that the execution environment may have an impact on the results. Tools that 125 | are e.g. single-threaded may seem to perform better in environments with few 126 | cores, while the reverse may be the case for tools with a high level of 127 | parallelism and an execution environment with 128+ cores. 128 | 129 | ## Adding a New Tool 130 | 131 | In order to include a tool in this repository, you should add a script for that 132 | tool under `tools/.sh`. You will also need to add a script 133 | `tools/_version.sh`. Then, add a line to `bench.py` that explains to 134 | the script how your tool is used. 135 | 136 | Your main shell script should output: 137 | 138 | - "safe": if the contract contains no reachable assertion violations 139 | - "unsafe": if the contract contains at least one reachable assertion violation 140 | - "unknown": if the tool was unable to determine whether a reachable assertion violation is present 141 | 142 | Before executing the benchmarks, `forge build` is invoked on all Solidity files 143 | in the repository, and tools that operate on EVM bytecode can read the compiled 144 | bytecode directly from the respective build outputs. 145 | 146 | Check out the examples for `hevm` and `halmos` in the repository for examples. 147 | Note that in order for others to run your tool, it needs to be added to 148 | `flake.nix`. 149 | 150 | ## Categories 151 | 152 | - conformance: should be easy, test correctness only 153 | - performance: should be hard 154 | 155 | [ ] loops 156 | [ ] calls 157 | [x] constructors 158 | [x] arithmetic 159 | [x] bitwise 160 | [ ] cheatcodes 161 | [x] memory 162 | [x] storage 163 | [x] keccak 164 | [x] calldata 165 | [ ] returndata 166 | [ ] address modeling 167 | 168 | - real world: 169 | [x] erc20 170 | [x] erc721 171 | [x] deposit 172 | [x] amm 173 | -------------------------------------------------------------------------------- /bench.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess 4 | from pathlib import Path 5 | import time 6 | import os 7 | import stat 8 | import json 9 | import re 10 | import random 11 | from typing import Literal 12 | import optparse 13 | from time import gmtime, strftime 14 | import csv 15 | 16 | 17 | def recreate_out() -> None: 18 | os.system("rm -rf out") 19 | try: 20 | os.mkdir("out") 21 | except FileExistsError: 22 | pass 23 | 24 | 25 | def build_forge() -> None: 26 | if not opts.norebuild: 27 | print("Building with forge...") 28 | recreate_out() 29 | cmd_line = ["forge", "build", "--ast", 30 | "--extra-output", "storageLayout", "metadata"] 31 | if opts.yul: cmd_line.extend(["--extra-output", "ir"]) 32 | cmd_line.extend(["--use", opts.solc_version]) 33 | ret = subprocess.run(cmd_line, capture_output=True) 34 | if ret.returncode != 0: 35 | print("Forge returned error(s)") 36 | print(printable_output(ret.stderr.decode("utf-8"))) 37 | ret.check_returncode() 38 | 39 | # TODO: this setup time should be reflected in the kontrol results somehow 40 | def build_kontrol() -> None: 41 | if "kontrol" not in get_tools_used(): 42 | return None 43 | if opts.norebuild: 44 | return None 45 | 46 | print("Building with kontrol...") 47 | cmd_line = ["kontrol", "build"] 48 | ret = subprocess.run(cmd_line, capture_output=True) 49 | if ret.returncode != 0: 50 | print("Kontrol returned error(s)") 51 | print(printable_output(ret.stderr.decode("utf-8"))) 52 | ret.check_returncode() 53 | 54 | available_tools = { 55 | "hevm-cvc5": { 56 | "call": "tools/hevm.sh", 57 | "version": "tools/hevm_version.sh", 58 | "extra_opts": ["--solver", "cvc5"], 59 | }, 60 | "hevm-z3": { 61 | "call": "tools/hevm.sh", 62 | "version": "tools/hevm_version.sh", 63 | "extra_opts": ["--solver","z3"], 64 | }, 65 | "hevm-bitwuzla": { 66 | "call": "tools/hevm.sh", 67 | "version": "tools/hevm_version.sh", 68 | "extra_opts": ["--solver","bitwuzla"], 69 | }, 70 | "halmos": { 71 | "call": "tools/halmos.sh", 72 | "version": "tools/halmos_version.sh", 73 | "extra_opts": [], 74 | }, 75 | "kontrol": { 76 | "call": "tools/kontrol.sh", 77 | "version": "tools/kontrol_version.sh", 78 | "extra_opts": [], 79 | } 80 | } 81 | 82 | 83 | global opts 84 | 85 | 86 | # make output printable to console by replacing special characters 87 | def printable_output(out): 88 | return ("%s" % out).replace('\\n', '\n').replace('\\t', '\t') 89 | 90 | 91 | def get_signature(fun: str, inputs) -> str: 92 | ret = [e["internalType"] for e in inputs] 93 | 94 | args = ",".join(ret) 95 | return f"{fun}({args})" 96 | 97 | 98 | # get all functions that start with 'prove' or 'check' 99 | def get_relevant_funcs(js) -> list[(str,str)]: 100 | ret = [] 101 | for i in range(len(js["abi"])): 102 | if "name" not in js["abi"][i]: 103 | continue 104 | fun = js["abi"][i]["name"] 105 | sig = get_signature(fun, js["abi"][i]["inputs"]) 106 | if re.match("^prove", fun) or re.match("^check", fun): 107 | ret.append((fun, sig)) 108 | 109 | return ret 110 | 111 | 112 | # determines whether dstest or not 113 | def determine_dstest(sol_file: str) -> bool: 114 | if sol_file.startswith("src/safe/ds-test") or sol_file.startswith("src/unsafe/ds-test"): 115 | return True 116 | elif sol_file.startswith("src/safe/1tx-abstract") or sol_file.startswith("src/unsafe/1tx-abstract"): 117 | return False 118 | else: 119 | raise ValueError( 120 | "solidity file is neither in 'ds-test' nor in '1tx-abstract' directory: " + sol_file 121 | ) 122 | 123 | 124 | # determines whether or not a given test case is expected to be safe or unsafe 125 | def determine_expected(sol_file: str) -> Literal["safe"] | Literal["unsafe"]: 126 | if sol_file.startswith("src/safe"): 127 | return "safe" 128 | elif sol_file.startswith("src/unsafe"): 129 | return "unsafe" 130 | else: 131 | raise ValueError( 132 | "solidity file is not in the safe or unsafe directories: " + sol_file 133 | ) 134 | 135 | 136 | # A case to solve by the solvers 137 | class Case: 138 | def __init__(self, contract: str, json_fname: str, sol_file: str, 139 | ds: bool, fun: str, sig: str): 140 | self.contract = contract 141 | self.json_fname = json_fname 142 | self.sol_file = sol_file 143 | self.ds = ds 144 | self.fun = fun 145 | self.sig = sig 146 | self.expected = determine_expected(sol_file) 147 | 148 | def get_name(self) -> str: 149 | return "%s:%s:%s" % (self.sol_file, self.contract, self.fun) 150 | 151 | def __str__(self): 152 | out = "" 153 | out += "Contr: %-25s " % self.contract 154 | out += "Fun: %-20s " % self.fun 155 | out += "sig: %-20s " % self.sig 156 | out += "DS: %s " % self.ds 157 | out += "Safe: %s" % self.expected 158 | # out += "JSON filename: %s " % self.json_fname 159 | return out 160 | 161 | 162 | # builds a mapping from solidity files to lists of contracts. we do this by 163 | # parsing the foundry build output, since that's easier than parsing the actual 164 | # solidity code to handle the case where a single solidity file contains 165 | # multiple contracts 166 | def gather_cases() -> list[Case]: 167 | build_forge() 168 | build_kontrol() 169 | # build a dictionary where the key is a directory in the foundry build 170 | # output, and the value is a list of contract names defined within 171 | output_jsons = { 172 | str(f): [j.stem for j in f.glob("*.json")] 173 | for f in Path("./out").iterdir() 174 | if f.is_dir() and f.name != "kompiled" 175 | } 176 | 177 | # replace the path to the output json with the path to the original solidity file 178 | cases: list[Case] = [] 179 | for out_dir, contracts in output_jsons.items(): 180 | for c in contracts: 181 | json_path = f"{out_dir}/{c}.json" 182 | if json_path.startswith("out/build-info"): 183 | continue 184 | with open(json_path) as oj: 185 | if opts.verbose: 186 | print("Parsing: ", json_path) 187 | js = json.load(oj) 188 | sol_file: str = js["ast"]["absolutePath"] 189 | if sol_file.startswith("src/common/") or sol_file.startswith("lib/"): 190 | continue 191 | ds_test = determine_dstest(sol_file) 192 | for f_and_s in get_relevant_funcs(js): 193 | fname = os.path.basename(sol_file) 194 | casename = f"{fname}:{c}:{f_and_s[0]}" 195 | if opts.verbose: 196 | print("Matching test pattern against: ", casename) 197 | if re.match(opts.testpattern, casename): 198 | cases.append(Case(c, json_path, sol_file, ds_test, 199 | f_and_s[0], f_and_s[1])) 200 | return cases 201 | 202 | 203 | # Generates a unique temporary file. Can be run multi-threaded 204 | def unique_file(fname_begin, fname_end=".out"): 205 | counter = 1 206 | while 1: 207 | fname = "out/" + fname_begin + '_' + str(counter) + fname_end 208 | try: 209 | fd = os.open( 210 | fname, os.O_CREAT | os.O_EXCL, stat.S_IREAD | stat.S_IWRITE) 211 | os.fdopen(fd).close() 212 | return fname 213 | except OSError: 214 | pass 215 | 216 | counter += 1 217 | if counter > 300: 218 | print("Cannot create unique_file, last try was: %s" % fname) 219 | exit(-1) 220 | 221 | print("ERROR: Cannot create unique temporary file") 222 | exit(-1) 223 | 224 | 225 | def last_line_in_file(fname: str) -> str: 226 | with open(fname, 'r') as f: 227 | lines = f.read().splitlines() 228 | last_line = lines[-1] 229 | return last_line 230 | 231 | 232 | # Result from a solver 233 | class Result: 234 | def __init__(self, result: str, mem_used_MB: float|None, exit_status: int|None, 235 | perc_CPU: int|None, t: float|None, tout: float|None, memoutMB: float|None, 236 | case: Case, out:str): 237 | self.result = result 238 | self.exit_status = exit_status 239 | self.mem_used_MB = mem_used_MB 240 | self.perc_CPU = perc_CPU 241 | self.t = t 242 | self.tout = tout 243 | self.memoutMB = memoutMB 244 | self.case = case 245 | self.out = out 246 | 247 | 248 | # executes the given tool script against the given test case and returns the 249 | # result 250 | def execute_case(tool: str, extra_opts: list[str], case: Case) -> Result: 251 | time_taken = None 252 | result = None 253 | res = None 254 | before = time.time_ns() 255 | fname_time = unique_file("output") 256 | toexec = ["time", "--verbose", "-o", "%s" % fname_time, 257 | tool, case.sol_file, case.contract, case.fun, case.sig, 258 | "%i" % case.ds, "%s" % opts.timeout, "%s" % (opts.memoutMB), "%d" % (opts.dump_smt)] 259 | toexec.extend(extra_opts) 260 | print("Running: %s" % (" ".join(toexec))) 261 | res = subprocess.run(toexec, capture_output=True, encoding="utf-8") 262 | after = time.time_ns() 263 | out_of_time = False 264 | mem_used_MB = None 265 | exit_status = None 266 | perc_CPU = None 267 | 268 | if opts.verbose: 269 | print("Res stdout is:", res.stdout) 270 | print("Res stderr is:", res.stderr) 271 | for line in res.stdout.split("\n"): 272 | line = line.strip() 273 | match = re.match("result: (.*)$", line) 274 | if match: 275 | result = match.group(1) 276 | time_taken = (after - before) / 1_000_000_000 277 | if out_of_time or result is None: 278 | result = "unknown" 279 | 280 | # parse `time --verbose` output 281 | with open(fname_time, 'r') as f: 282 | for line in f: 283 | line = line.strip() 284 | match = re.match(r"Maximum resident set size .kbytes.: (.*)", line) 285 | if match: 286 | mem_used_MB = int(match.group(1))/1000 287 | 288 | match = re.match(r"Percent of CPU this job got: (.*)%", line) 289 | if match: 290 | perc_CPU = int(match.group(1)) 291 | 292 | match = re.match(r"Exit status:[ ]*(.*)[ ]*$", line) 293 | if match: 294 | exit_status = int(match.group(1)) 295 | 296 | assert result == "safe" or result == "unsafe" or result == "unknown" 297 | os.unlink(fname_time) 298 | if opts.verbose: 299 | print("Result is: ", result) 300 | 301 | return Result(result=result, mem_used_MB=mem_used_MB, 302 | perc_CPU=perc_CPU, exit_status=exit_status, 303 | t=time_taken, tout=opts.timeout, memoutMB=opts.memoutMB, case=case, out=res.stderr) 304 | 305 | 306 | def get_version(script: str) -> str: 307 | toexec = [script] 308 | print("Running: %s" % (" ".join(toexec))) 309 | res = subprocess.run(toexec, capture_output=True, encoding="utf-8") 310 | res.check_returncode() 311 | return res.stdout.rstrip() 312 | 313 | 314 | # executes all tests contained in the argument cases mapping with all tools and 315 | # builds the result dict 316 | def run_all_tests(tools, cases: list[Case]) -> dict[str, list[Result]]: 317 | results: dict[str, list[Result]] = {} 318 | for tool, descr in tools.items(): 319 | version = get_version(descr["version"]) 320 | res = [] 321 | for c in cases: 322 | res.append(execute_case(descr["call"], descr["extra_opts"], c)) 323 | name = "%s-%s-tstamp-%s" % (tool, version, opts.timestamp) 324 | results[name] = res 325 | return results 326 | 327 | 328 | # Encodes the 'Result' class into JSON 329 | class ResultEncoder(json.JSONEncoder): 330 | def default(self, o): 331 | if isinstance(o, Result): 332 | solved = o.result == "safe" or o.result == "unsafe" 333 | if solved: 334 | correct = o.result == o.case.expected 335 | else: 336 | correct = None 337 | return { 338 | "name": o.case.get_name(), 339 | "solc_version": opts.solc_version, 340 | "ds": o.case.ds, 341 | "solved": solved, 342 | "correct": correct, 343 | "t": o.t, 344 | "tout": o.tout, 345 | "memMB": o.mem_used_MB, 346 | "exit_status": o.exit_status, 347 | "out": o.out} 348 | return json.JSONEncoder.default(self, o) 349 | 350 | 351 | # Returns empty string if value is None. This is converted to NULL in SQLite 352 | def empty_if_none(x: None|int|float) -> str: 353 | if x is None: 354 | return "" 355 | else: 356 | return "%s" % x 357 | 358 | 359 | # Dump results in SQLite and CSV, so they can be analyzed via 360 | # SQL/Libreoffice/commend line 361 | def dump_results(solvers_results: dict[str, list[Result]], fname: str): 362 | with open("%s.json" % fname, "w") as f: 363 | f.write(json.dumps(solvers_results, indent=2, cls=ResultEncoder)) 364 | with open("%s.csv" % fname, "w", newline='') as f: 365 | fieldnames = ["solver", "solc_version", "name", "fun", "sig", "result", "correct", "t", "timeout", "memoutMB", "memMB", "exit_status", "output"] 366 | writer = csv.DictWriter(f, fieldnames=fieldnames) 367 | writer.writeheader() 368 | for solver, results in solvers_results.items(): 369 | for r in results: 370 | corr_as_sqlite = "" 371 | if r.result is not None: 372 | corr_as_sqlite = (int)(r.result == r.case.expected) 373 | writer.writerow({ 374 | "solver": solver, "solc_version": opts.solc_version, 375 | "name": r.case.get_name(), "fun": r.case.fun, 376 | "sig": r.case.sig, "result": r.result, 377 | "correct": corr_as_sqlite, "t": empty_if_none(r.t), 378 | "timeout": r.tout, 379 | "memoutMB": r.memoutMB, 380 | "memMB": empty_if_none(r.mem_used_MB), 381 | "exit_status": empty_if_none(r.exit_status), "output": r.out}) 382 | 383 | 384 | # --- main --- 385 | 386 | 387 | # Set up options for main 388 | def set_up_parser() -> optparse.OptionParser: 389 | usage = "usage: %prog [options]" 390 | desc = """Run all benchmarks for all tools 391 | """ 392 | 393 | parser = optparse.OptionParser(usage=usage, description=desc) 394 | parser.add_option("--verbose", "-v", action="store_true", default=False, 395 | dest="verbose", help="More verbose output. Default: %default") 396 | 397 | parser.add_option("-s", dest="seed", type=int, default=1, 398 | help="Seed for random numbers. Default: %default") 399 | 400 | parser.add_option("--tests", dest="testpattern", type=str, default=".*", 401 | help="Test pattern regexp in the format 'fname:contract:function'. Default: %default") 402 | 403 | avail = ", ".join([t for t,_ in available_tools.items()]) 404 | parser.add_option("--tools", dest="tools", type=str, default="all", 405 | help="Only run these tools (comma separated list). Available tools: %s" % avail) 406 | 407 | parser.add_option("--solcv", dest="solc_version", type=str, default="0.8.19", 408 | help="solc version to use to compile contracts") 409 | 410 | parser.add_option("--dumpsmt", dest="dump_smt", default=False, 411 | action="store_true", help="Ask the solver to dump SMT files, if the solver supports it") 412 | 413 | parser.add_option("-t", dest="timeout", type=int, default=25, 414 | help="Max time to run. Default: %default") 415 | 416 | parser.add_option("-m", dest="memoutMB", type=int, default=16000, 417 | help="Max memory per execution of the tool, in MB. Note that if your tool uses 16 threads, each 100MB, it will be counted as 1600MB. Default: %default") 418 | 419 | parser.add_option("--limit", dest="limit", type=int, default=100000, 420 | help="Max number of cases to run. Default: %default") 421 | 422 | parser.add_option("--norebuild", dest="norebuild", default=False, 423 | action="store_true", help="Don't rebuild with forge") 424 | 425 | parser.add_option("--yul", action="store_true", default=False, 426 | dest="yul", help="Build through YUL pipeline in forge. You can then access the YUL via `cat myjson | jq '.ir'`.") 427 | 428 | return parser 429 | 430 | 431 | def get_tools_used(): 432 | ret = {} 433 | for tool in opts.tools.split(","): 434 | tool=tool.strip() 435 | if tool == "all": 436 | ret = available_tools 437 | break 438 | if tool == "": 439 | continue 440 | if tool not in available_tools: 441 | print("ERROR: tool you specified, '%s' is not known." % tool) 442 | print("Known tools: %s" % ", ".join([t for t, _ in available_tools.items()])) 443 | exit(-1) 444 | else: 445 | ret[tool] = available_tools[tool] 446 | 447 | return ret 448 | 449 | def main() -> None: 450 | parser = set_up_parser() 451 | global opts 452 | (opts, args) = parser.parse_args() 453 | if len(args) > 0: 454 | print("Benchmarking does not accept arguments") 455 | exit(-1) 456 | random.seed(opts.seed) 457 | opts.timestamp = strftime("%Y-%m-%d-%H:%M", gmtime()) 458 | tools_used = get_tools_used() 459 | print("Will run tool(s): %s" % ", ".join([t for t, _ in tools_used.items()])) 460 | if len(tools_used) == 0: 461 | print("ERROR: You selected no tools to run. Exiting.") 462 | exit(-1) 463 | 464 | cases = gather_cases() 465 | print(f"running {len(cases)} cases") 466 | cases.sort(key=lambda contr: contr.get_name()) 467 | if len(cases) == 0: 468 | print(f"No cases gathered with test pattern '{opts.testpattern}'. Exiting.") 469 | exit(0) 470 | print(f"Cases gathered given test pattern '{opts.testpattern}':") 471 | for c in cases: 472 | print("-> %s" % c) 473 | random.shuffle(cases) 474 | solvers_results = run_all_tests(tools_used, cases[:opts.limit]) 475 | results_fname = "results-tstamp-%s" % opts.timestamp 476 | dump_results(solvers_results, results_fname) 477 | os.system("cp %s.csv results-latest.csv" % results_fname) 478 | os.system("cp %s.json results-latest.json" % results_fname) 479 | print("Generated file %s.csv" % results_fname) 480 | print("Generated file %s.json" % results_fname) 481 | os.system("sqlite3 results.db < create_table.sql") 482 | os.system("sqlite3 results.db \". mode csv\" \". import -skip 1 %s.csv results\" \".exit\" " % results_fname) 483 | os.system("sqlite3 results.db < clean_table.sql") 484 | 485 | if __name__ == "__main__": 486 | main() 487 | -------------------------------------------------------------------------------- /check_results.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json 4 | 5 | def find_incorrect_test_cases_by_solver(file_path): 6 | with open(file_path, 'r') as file: 7 | data = json.load(file) 8 | 9 | incorrect_results = {} 10 | 11 | for solver, results in data.items(): 12 | incorrect_test_cases = [] 13 | for result in results: 14 | if result.get('correct', None) is False: 15 | incorrect_test_cases.append(result['name']) 16 | 17 | if incorrect_test_cases: 18 | incorrect_results[solver] = incorrect_test_cases 19 | 20 | return incorrect_results 21 | 22 | # Replace 'your_file.json' with the path to your JSON file 23 | file_path = 'results-latest.json' 24 | incorrect_results = find_incorrect_test_cases_by_solver(file_path) 25 | 26 | print("Incorrect test cases by solver:") 27 | for solver, test_cases in incorrect_results.items(): 28 | print(f"\nSolver: {solver}") 29 | for test_case in test_cases: 30 | print(f" - {test_case}") 31 | -------------------------------------------------------------------------------- /clean_table.sql: -------------------------------------------------------------------------------- 1 | UPDATE results SET correct = NULL WHERE correct = ''; 2 | UPDATE results SET t = NULL WHERE t = ''; 3 | UPDATE results SET memMB = NULL WHERE memMB = ''; 4 | UPDATE results SET exit_status = NULL WHERE exit_status = ''; 5 | -------------------------------------------------------------------------------- /clear-results.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -f ./*.json 4 | rm -f ./*.csv 5 | rm -f ./*.db 6 | 7 | rm -rf halmos-smt2 8 | rm -rf hevm-smt2 9 | -------------------------------------------------------------------------------- /create_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS results ( 2 | solver STRING NOT NULL, 3 | solc_version STRING NOT NULL, 4 | name STRING NOT NULL, 5 | fun STRING NOT NULL, 6 | sig STRING NOT NULL, 7 | result STRING NOT NULL, 8 | 9 | correct INT, 10 | t FLOAT, 11 | tout FLOAT, 12 | memoutMB FLOAT, 13 | memMB FLOAT, 14 | exit_status INT, 15 | output STRING 16 | ); 17 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "ate-pairing": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1499347915, 7 | "narHash": "sha256-IMfWgKkkX7UcUaOPtGRcYD8MdVEP5Z9JOOSJ4+P07G8=", 8 | "owner": "herumi", 9 | "repo": "ate-pairing", 10 | "rev": "e69890125746cdaf25b5b51227d96678f76479fe", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "herumi", 15 | "repo": "ate-pairing", 16 | "rev": "e69890125746cdaf25b5b51227d96678f76479fe", 17 | "type": "github" 18 | } 19 | }, 20 | "bitwuzla-pkgs": { 21 | "locked": { 22 | "lastModified": 1705518710, 23 | "narHash": "sha256-4AV1Q3YEYakWcsu3nSX3U0hXZAcir0maGFrG5sEU/Kk=", 24 | "owner": "d-xo", 25 | "repo": "nixpkgs", 26 | "rev": "94e802bce3a1bc05b3acfc5e876de15fd2ecb564", 27 | "type": "github" 28 | }, 29 | "original": { 30 | "owner": "d-xo", 31 | "repo": "nixpkgs", 32 | "rev": "94e802bce3a1bc05b3acfc5e876de15fd2ecb564", 33 | "type": "github" 34 | } 35 | }, 36 | "blockchain-k-plugin": { 37 | "inputs": { 38 | "ate-pairing": "ate-pairing", 39 | "cpp-httplib": "cpp-httplib", 40 | "cryptopp": "cryptopp", 41 | "flake-utils": [ 42 | "kontrol", 43 | "kevm", 44 | "k-framework", 45 | "flake-utils" 46 | ], 47 | "libff": "libff", 48 | "nixpkgs": [ 49 | "kontrol", 50 | "kevm", 51 | "k-framework", 52 | "nixpkgs" 53 | ], 54 | "secp256k1": "secp256k1", 55 | "xbyak": "xbyak" 56 | }, 57 | "locked": { 58 | "lastModified": 1696357247, 59 | "narHash": "sha256-cCsH/uM1lfuCxfgflVwtg61Il6WjdZ/mDNwijIaRiRo=", 60 | "owner": "runtimeverification", 61 | "repo": "blockchain-k-plugin", 62 | "rev": "b42e6ede9f6b72cedabc519810416e2994caad45", 63 | "type": "github" 64 | }, 65 | "original": { 66 | "owner": "runtimeverification", 67 | "repo": "blockchain-k-plugin", 68 | "rev": "b42e6ede9f6b72cedabc519810416e2994caad45", 69 | "type": "github" 70 | } 71 | }, 72 | "booster-backend": { 73 | "inputs": { 74 | "haskell-backend": [ 75 | "kontrol", 76 | "kevm", 77 | "k-framework", 78 | "haskell-backend" 79 | ], 80 | "nixpkgs": [ 81 | "kontrol", 82 | "kevm", 83 | "k-framework", 84 | "haskell-backend", 85 | "nixpkgs" 86 | ], 87 | "stacklock2nix": [ 88 | "kontrol", 89 | "kevm", 90 | "k-framework", 91 | "haskell-backend", 92 | "stacklock2nix" 93 | ] 94 | }, 95 | "locked": { 96 | "lastModified": 1704898943, 97 | "narHash": "sha256-sMSJKwNZg+tzr4DlOiP76eke2AgFAUMwRp0klRW5/24=", 98 | "owner": "runtimeverification", 99 | "repo": "hs-backend-booster", 100 | "rev": "6dd78c2adbc325f823809dab085617a83d5f8ec7", 101 | "type": "github" 102 | }, 103 | "original": { 104 | "owner": "runtimeverification", 105 | "repo": "hs-backend-booster", 106 | "rev": "6dd78c2adbc325f823809dab085617a83d5f8ec7", 107 | "type": "github" 108 | } 109 | }, 110 | "cabal-head": { 111 | "flake": false, 112 | "locked": { 113 | "lastModified": 1693777827, 114 | "narHash": "sha256-zMwVwTztoQGrNIJSxSdVJjYN77rleRjpC+K5AoIl7po=", 115 | "owner": "haskell", 116 | "repo": "cabal", 117 | "rev": "24a4603eebfcf7730f00bb69a02d1568990798d5", 118 | "type": "github" 119 | }, 120 | "original": { 121 | "owner": "haskell", 122 | "repo": "cabal", 123 | "type": "github" 124 | } 125 | }, 126 | "cpp-httplib": { 127 | "flake": false, 128 | "locked": { 129 | "lastModified": 1595279716, 130 | "narHash": "sha256-cZW/iEwlaB4UQ0OQLNqboY9addncIM/OsxxPzqmATmE=", 131 | "owner": "yhirose", 132 | "repo": "cpp-httplib", 133 | "rev": "72ce293fed9f9335e92c95ab7d085feed18c0ee8", 134 | "type": "github" 135 | }, 136 | "original": { 137 | "owner": "yhirose", 138 | "repo": "cpp-httplib", 139 | "rev": "72ce293fed9f9335e92c95ab7d085feed18c0ee8", 140 | "type": "github" 141 | } 142 | }, 143 | "cryptopp": { 144 | "flake": false, 145 | "locked": { 146 | "lastModified": 1632484127, 147 | "narHash": "sha256-a3TYaK34WvKEXN7LKAfGwQ3ZL6a3k/zMZyyVfnkQqO4=", 148 | "owner": "weidai11", 149 | "repo": "cryptopp", 150 | "rev": "69bf6b53052b59ccb57ce068ce741988ae087317", 151 | "type": "github" 152 | }, 153 | "original": { 154 | "owner": "weidai11", 155 | "repo": "cryptopp", 156 | "rev": "69bf6b53052b59ccb57ce068ce741988ae087317", 157 | "type": "github" 158 | } 159 | }, 160 | "ethereum-legacytests": { 161 | "flake": false, 162 | "locked": { 163 | "lastModified": 1633533576, 164 | "narHash": "sha256-43lkAi6Z492pbHV+jySYXzvNtoGEGsndnBkxfYgmqFM=", 165 | "owner": "ethereum", 166 | "repo": "legacytests", 167 | "rev": "d7abc42a7b352a7b44b1f66b58aca54e4af6a9d7", 168 | "type": "github" 169 | }, 170 | "original": { 171 | "owner": "ethereum", 172 | "repo": "legacytests", 173 | "rev": "d7abc42a7b352a7b44b1f66b58aca54e4af6a9d7", 174 | "type": "github" 175 | } 176 | }, 177 | "ethereum-tests": { 178 | "flake": false, 179 | "locked": { 180 | "lastModified": 1682506704, 181 | "narHash": "sha256-PyFZhdUQRUbmohoMS+w41rmSFfKeTO2RqHLetWlU4Jw=", 182 | "owner": "ethereum", 183 | "repo": "tests", 184 | "rev": "b25623d4d7df10e38498cace7adc7eb413c4b20d", 185 | "type": "github" 186 | }, 187 | "original": { 188 | "owner": "ethereum", 189 | "ref": "v12.2", 190 | "repo": "tests", 191 | "type": "github" 192 | } 193 | }, 194 | "ethereum-tests_2": { 195 | "flake": false, 196 | "locked": { 197 | "lastModified": 1637090666, 198 | "narHash": "sha256-B3fMpp/dpnwtCQzxI45kptJVwIM/kMo9hptcMP2B5n0=", 199 | "owner": "ethereum", 200 | "repo": "tests", 201 | "rev": "6401889dec4eee58e808fd178fb2c7f628a3e039", 202 | "type": "github" 203 | }, 204 | "original": { 205 | "owner": "ethereum", 206 | "repo": "tests", 207 | "rev": "6401889dec4eee58e808fd178fb2c7f628a3e039", 208 | "type": "github" 209 | } 210 | }, 211 | "flake-compat": { 212 | "flake": false, 213 | "locked": { 214 | "lastModified": 1673956053, 215 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", 216 | "owner": "edolstra", 217 | "repo": "flake-compat", 218 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", 219 | "type": "github" 220 | }, 221 | "original": { 222 | "owner": "edolstra", 223 | "repo": "flake-compat", 224 | "type": "github" 225 | } 226 | }, 227 | "flake-utils": { 228 | "inputs": { 229 | "systems": "systems" 230 | }, 231 | "locked": { 232 | "lastModified": 1689068808, 233 | "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", 234 | "owner": "numtide", 235 | "repo": "flake-utils", 236 | "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", 237 | "type": "github" 238 | }, 239 | "original": { 240 | "owner": "numtide", 241 | "repo": "flake-utils", 242 | "type": "github" 243 | } 244 | }, 245 | "flake-utils_2": { 246 | "locked": { 247 | "lastModified": 1644229661, 248 | "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", 249 | "owner": "numtide", 250 | "repo": "flake-utils", 251 | "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797", 252 | "type": "github" 253 | }, 254 | "original": { 255 | "owner": "numtide", 256 | "repo": "flake-utils", 257 | "type": "github" 258 | } 259 | }, 260 | "flake-utils_3": { 261 | "inputs": { 262 | "systems": "systems_2" 263 | }, 264 | "locked": { 265 | "lastModified": 1692799911, 266 | "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", 267 | "owner": "numtide", 268 | "repo": "flake-utils", 269 | "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", 270 | "type": "github" 271 | }, 272 | "original": { 273 | "owner": "numtide", 274 | "repo": "flake-utils", 275 | "type": "github" 276 | } 277 | }, 278 | "flake-utils_4": { 279 | "locked": { 280 | "lastModified": 1644229661, 281 | "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", 282 | "owner": "numtide", 283 | "repo": "flake-utils", 284 | "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797", 285 | "type": "github" 286 | }, 287 | "original": { 288 | "owner": "numtide", 289 | "repo": "flake-utils", 290 | "type": "github" 291 | } 292 | }, 293 | "flake-utils_5": { 294 | "inputs": { 295 | "systems": "systems_3" 296 | }, 297 | "locked": { 298 | "lastModified": 1701680307, 299 | "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", 300 | "owner": "numtide", 301 | "repo": "flake-utils", 302 | "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", 303 | "type": "github" 304 | }, 305 | "original": { 306 | "owner": "numtide", 307 | "repo": "flake-utils", 308 | "type": "github" 309 | } 310 | }, 311 | "flake-utils_6": { 312 | "inputs": { 313 | "systems": "systems_4" 314 | }, 315 | "locked": { 316 | "lastModified": 1694529238, 317 | "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 318 | "owner": "numtide", 319 | "repo": "flake-utils", 320 | "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 321 | "type": "github" 322 | }, 323 | "original": { 324 | "owner": "numtide", 325 | "repo": "flake-utils", 326 | "type": "github" 327 | } 328 | }, 329 | "fmt-src": { 330 | "flake": false, 331 | "locked": { 332 | "lastModified": 1661615830, 333 | "narHash": "sha256-rP6ymyRc7LnKxUXwPpzhHOQvpJkpnRFOt2ctvUNlYI0=", 334 | "owner": "fmtlib", 335 | "repo": "fmt", 336 | "rev": "a33701196adfad74917046096bf5a2aa0ab0bb50", 337 | "type": "github" 338 | }, 339 | "original": { 340 | "owner": "fmtlib", 341 | "ref": "9.1.0", 342 | "repo": "fmt", 343 | "type": "github" 344 | } 345 | }, 346 | "forge-std": { 347 | "flake": false, 348 | "locked": { 349 | "lastModified": 1702656582, 350 | "narHash": "sha256-qDC/3x/CfeghsqftBJme4IlNGDG7YgV9cx8I5tc30KA=", 351 | "owner": "foundry-rs", 352 | "repo": "forge-std", 353 | "rev": "155d547c449afa8715f538d69454b83944117811", 354 | "type": "github" 355 | }, 356 | "original": { 357 | "owner": "foundry-rs", 358 | "repo": "forge-std", 359 | "type": "github" 360 | } 361 | }, 362 | "foundry": { 363 | "inputs": { 364 | "flake-utils": "flake-utils_2", 365 | "nixpkgs": "nixpkgs" 366 | }, 367 | "locked": { 368 | "lastModified": 1722676286, 369 | "narHash": "sha256-wEDJdvwRZF2ErQ33nQ0Lqn/48XrPbaadv56/bM2MSZU=", 370 | "owner": "shazow", 371 | "repo": "foundry.nix", 372 | "rev": "d84c83b1c1722c8742b3d2d84c9386814d75384e", 373 | "type": "github" 374 | }, 375 | "original": { 376 | "owner": "shazow", 377 | "ref": "monthly", 378 | "repo": "foundry.nix", 379 | "type": "github" 380 | } 381 | }, 382 | "foundry_2": { 383 | "inputs": { 384 | "flake-utils": "flake-utils_4", 385 | "nixpkgs": "nixpkgs_2" 386 | }, 387 | "locked": { 388 | "lastModified": 1691140388, 389 | "narHash": "sha256-AH3fx2VFGPRSOjnuakab4T4AdUstwTnFTbnkoU4df8Q=", 390 | "owner": "shazow", 391 | "repo": "foundry.nix", 392 | "rev": "6089aad0ef615ac8c7b0c948d6052fa848c99523", 393 | "type": "github" 394 | }, 395 | "original": { 396 | "owner": "shazow", 397 | "ref": "monthly", 398 | "repo": "foundry.nix", 399 | "type": "github" 400 | } 401 | }, 402 | "foundry_3": { 403 | "inputs": { 404 | "flake-utils": [ 405 | "kontrol", 406 | "flake-utils" 407 | ], 408 | "nixpkgs": [ 409 | "kontrol", 410 | "nixpkgs" 411 | ] 412 | }, 413 | "locked": { 414 | "lastModified": 1705309865, 415 | "narHash": "sha256-HkTSsjmR3DE1xKr1M0bBWKyTl4f616166Przd2mwNxw=", 416 | "owner": "shazow", 417 | "repo": "foundry.nix", 418 | "rev": "883243b30a4b8dbb1b515b79b750e2caf7df1a79", 419 | "type": "github" 420 | }, 421 | "original": { 422 | "owner": "shazow", 423 | "ref": "monthly", 424 | "repo": "foundry.nix", 425 | "type": "github" 426 | } 427 | }, 428 | "halmos-src": { 429 | "flake": false, 430 | "locked": { 431 | "lastModified": 1723675074, 432 | "narHash": "sha256-ZXiP/CbvGz37QiF0UwvYryAVxIvVYiLW7RnKxPrtVP0=", 433 | "owner": "a16z", 434 | "repo": "halmos", 435 | "rev": "c3f45dd3ff65635840847713ece60b779f8600d5", 436 | "type": "github" 437 | }, 438 | "original": { 439 | "owner": "a16z", 440 | "repo": "halmos", 441 | "type": "github" 442 | } 443 | }, 444 | "haskell-backend": { 445 | "inputs": { 446 | "nixpkgs": "nixpkgs_4", 447 | "stacklock2nix": "stacklock2nix", 448 | "z3": "z3" 449 | }, 450 | "locked": { 451 | "lastModified": 1703063258, 452 | "narHash": "sha256-wL5kFsbs6RS+bsrf+SDq85DDKce/sZZ7VQDtJZA5JS0=", 453 | "owner": "runtimeverification", 454 | "repo": "haskell-backend", 455 | "rev": "ca05f14b7957fec9f2a5ab3444cae01c5a76f12f", 456 | "type": "github" 457 | }, 458 | "original": { 459 | "owner": "runtimeverification", 460 | "repo": "haskell-backend", 461 | "rev": "ca05f14b7957fec9f2a5ab3444cae01c5a76f12f", 462 | "type": "github" 463 | } 464 | }, 465 | "hevm": { 466 | "inputs": { 467 | "bitwuzla-pkgs": "bitwuzla-pkgs", 468 | "cabal-head": "cabal-head", 469 | "ethereum-tests": "ethereum-tests", 470 | "flake-compat": "flake-compat", 471 | "flake-utils": "flake-utils_3", 472 | "forge-std": "forge-std", 473 | "foundry": "foundry_2", 474 | "nixpkgs": "nixpkgs_3", 475 | "solidity": "solidity" 476 | }, 477 | "locked": { 478 | "lastModified": 1705518878, 479 | "narHash": "sha256-OT/u7PcK0CAt4P+hMQzscJB5cJ9fsnIdjo274mJc8Ac=", 480 | "owner": "ethereum", 481 | "repo": "hevm", 482 | "rev": "b419c12829f65391ee1423cedb215480718bf28b", 483 | "type": "github" 484 | }, 485 | "original": { 486 | "owner": "ethereum", 487 | "ref": "bitwuzla-main", 488 | "repo": "hevm", 489 | "type": "github" 490 | } 491 | }, 492 | "immer-src": { 493 | "flake": false, 494 | "locked": { 495 | "lastModified": 1634324349, 496 | "narHash": "sha256-1OicqmyM3Rcrs/jkRMip2pXITQnVDRrHbQbEpZZ4nnU=", 497 | "owner": "runtimeverification", 498 | "repo": "immer", 499 | "rev": "198c2ae260d49ef1800a2fe4433e07d7dec20059", 500 | "type": "github" 501 | }, 502 | "original": { 503 | "owner": "runtimeverification", 504 | "repo": "immer", 505 | "rev": "198c2ae260d49ef1800a2fe4433e07d7dec20059", 506 | "type": "github" 507 | } 508 | }, 509 | "k-framework": { 510 | "inputs": { 511 | "booster-backend": "booster-backend", 512 | "flake-utils": "flake-utils_5", 513 | "haskell-backend": "haskell-backend", 514 | "llvm-backend": "llvm-backend", 515 | "nixpkgs": [ 516 | "kontrol", 517 | "kevm", 518 | "k-framework", 519 | "haskell-backend", 520 | "nixpkgs" 521 | ], 522 | "rv-utils": "rv-utils" 523 | }, 524 | "locked": { 525 | "lastModified": 1704989061, 526 | "narHash": "sha256-9IJn0f0m5AFHEXAaapvp5RhtBqpr5nnj3CAtBy+kju4=", 527 | "owner": "runtimeverification", 528 | "repo": "k", 529 | "rev": "18ce58892d77b05f8aa51f42ae563ff068620ad3", 530 | "type": "github" 531 | }, 532 | "original": { 533 | "owner": "runtimeverification", 534 | "ref": "v6.1.77", 535 | "repo": "k", 536 | "type": "github" 537 | } 538 | }, 539 | "kevm": { 540 | "inputs": { 541 | "blockchain-k-plugin": "blockchain-k-plugin", 542 | "ethereum-legacytests": "ethereum-legacytests", 543 | "ethereum-tests": "ethereum-tests_2", 544 | "flake-utils": [ 545 | "kontrol", 546 | "kevm", 547 | "k-framework", 548 | "flake-utils" 549 | ], 550 | "haskell-backend": [ 551 | "kontrol", 552 | "kevm", 553 | "k-framework", 554 | "haskell-backend" 555 | ], 556 | "k-framework": "k-framework", 557 | "nixpkgs": [ 558 | "kontrol", 559 | "kevm", 560 | "k-framework", 561 | "nixpkgs" 562 | ], 563 | "nixpkgs-pyk": [ 564 | "kontrol", 565 | "kevm", 566 | "pyk", 567 | "nixpkgs" 568 | ], 569 | "poetry2nix": [ 570 | "kontrol", 571 | "kevm", 572 | "pyk", 573 | "poetry2nix" 574 | ], 575 | "pyk": "pyk", 576 | "rv-utils": [ 577 | "kontrol", 578 | "kevm", 579 | "k-framework", 580 | "rv-utils" 581 | ] 582 | }, 583 | "locked": { 584 | "lastModified": 1705310394, 585 | "narHash": "sha256-RsB9CD8FmtRvbm9Et/jF4rwxGDxlyqgjcm3t2lBQHoo=", 586 | "owner": "runtimeverification", 587 | "repo": "evm-semantics", 588 | "rev": "ba3f1ab1e2e320c26b1439afa0f6ea4fbf3f5d3b", 589 | "type": "github" 590 | }, 591 | "original": { 592 | "owner": "runtimeverification", 593 | "ref": "v1.0.416", 594 | "repo": "evm-semantics", 595 | "type": "github" 596 | } 597 | }, 598 | "kontrol": { 599 | "inputs": { 600 | "flake-utils": [ 601 | "kontrol", 602 | "kevm", 603 | "flake-utils" 604 | ], 605 | "foundry": "foundry_3", 606 | "k-framework": [ 607 | "kontrol", 608 | "kevm", 609 | "k-framework" 610 | ], 611 | "kevm": "kevm", 612 | "nixpkgs": [ 613 | "kontrol", 614 | "kevm", 615 | "nixpkgs" 616 | ], 617 | "nixpkgs-pyk": [ 618 | "kontrol", 619 | "kevm", 620 | "nixpkgs-pyk" 621 | ], 622 | "poetry2nix": [ 623 | "kontrol", 624 | "kevm", 625 | "poetry2nix" 626 | ], 627 | "pyk": [ 628 | "kontrol", 629 | "kevm", 630 | "pyk" 631 | ], 632 | "rv-utils": [ 633 | "kontrol", 634 | "kevm", 635 | "rv-utils" 636 | ], 637 | "solc": "solc" 638 | }, 639 | "locked": { 640 | "lastModified": 1705326132, 641 | "narHash": "sha256-QL6twwP0ZSS72bc9ZIE27upqYJKtfqQ/2mqFWOY2/+w=", 642 | "owner": "runtimeverification", 643 | "repo": "kontrol", 644 | "rev": "2f8e0a9783bba03e3d22a633dd9e40053c5915a7", 645 | "type": "github" 646 | }, 647 | "original": { 648 | "owner": "runtimeverification", 649 | "repo": "kontrol", 650 | "type": "github" 651 | } 652 | }, 653 | "libff": { 654 | "flake": false, 655 | "locked": { 656 | "lastModified": 1588032522, 657 | "narHash": "sha256-I0kH2XLvHDSrdL/o4i6XozWQJV0UDv9zH6+sWS0UQHg=", 658 | "owner": "scipr-lab", 659 | "repo": "libff", 660 | "rev": "5835b8c59d4029249645cf551f417608c48f2770", 661 | "type": "github" 662 | }, 663 | "original": { 664 | "owner": "scipr-lab", 665 | "repo": "libff", 666 | "rev": "5835b8c59d4029249645cf551f417608c48f2770", 667 | "type": "github" 668 | } 669 | }, 670 | "llvm-backend": { 671 | "inputs": { 672 | "fmt-src": "fmt-src", 673 | "immer-src": "immer-src", 674 | "mavenix": "mavenix", 675 | "nixpkgs": [ 676 | "kontrol", 677 | "kevm", 678 | "k-framework", 679 | "haskell-backend", 680 | "nixpkgs" 681 | ], 682 | "pybind11-src": "pybind11-src", 683 | "rapidjson-src": "rapidjson-src", 684 | "utils": [ 685 | "kontrol", 686 | "kevm", 687 | "k-framework", 688 | "flake-utils" 689 | ] 690 | }, 691 | "locked": { 692 | "lastModified": 1704904099, 693 | "narHash": "sha256-RpRhxWaW01qLJ51XCKiVmS6T5d9CI+uXMcxFxADy1wY=", 694 | "owner": "runtimeverification", 695 | "repo": "llvm-backend", 696 | "rev": "276afab5392f7a3ed000adaacfd9eea9f9825672", 697 | "type": "github" 698 | }, 699 | "original": { 700 | "owner": "runtimeverification", 701 | "repo": "llvm-backend", 702 | "type": "github" 703 | } 704 | }, 705 | "mavenix": { 706 | "inputs": { 707 | "nixpkgs": "nixpkgs_5", 708 | "utils": "utils" 709 | }, 710 | "locked": { 711 | "lastModified": 1643802645, 712 | "narHash": "sha256-BynM25iwp/l3FyrcHqiNJdDxvN6IxSM3/zkFR6PD3B0=", 713 | "owner": "nix-community", 714 | "repo": "mavenix", 715 | "rev": "ce9ddfd7f361190e8e8dcfaf6b8282eebbb3c7cb", 716 | "type": "github" 717 | }, 718 | "original": { 719 | "owner": "nix-community", 720 | "repo": "mavenix", 721 | "type": "github" 722 | } 723 | }, 724 | "nix-github-actions": { 725 | "inputs": { 726 | "nixpkgs": [ 727 | "kontrol", 728 | "kevm", 729 | "pyk", 730 | "poetry2nix", 731 | "nixpkgs" 732 | ] 733 | }, 734 | "locked": { 735 | "lastModified": 1693660503, 736 | "narHash": "sha256-B/g2V4v6gjirFmy+I5mwB2bCYc0l3j5scVfwgl6WOl8=", 737 | "owner": "nix-community", 738 | "repo": "nix-github-actions", 739 | "rev": "bd5bdbb52350e145c526108f4ef192eb8e554fa0", 740 | "type": "github" 741 | }, 742 | "original": { 743 | "owner": "nix-community", 744 | "repo": "nix-github-actions", 745 | "type": "github" 746 | } 747 | }, 748 | "nixpkgs": { 749 | "locked": { 750 | "lastModified": 1666753130, 751 | "narHash": "sha256-Wff1dGPFSneXJLI2c0kkdWTgxnQ416KE6X4KnFkgPYQ=", 752 | "owner": "NixOS", 753 | "repo": "nixpkgs", 754 | "rev": "f540aeda6f677354f1e7144ab04352f61aaa0118", 755 | "type": "github" 756 | }, 757 | "original": { 758 | "id": "nixpkgs", 759 | "type": "indirect" 760 | } 761 | }, 762 | "nixpkgs_2": { 763 | "locked": { 764 | "lastModified": 1666753130, 765 | "narHash": "sha256-Wff1dGPFSneXJLI2c0kkdWTgxnQ416KE6X4KnFkgPYQ=", 766 | "owner": "NixOS", 767 | "repo": "nixpkgs", 768 | "rev": "f540aeda6f677354f1e7144ab04352f61aaa0118", 769 | "type": "github" 770 | }, 771 | "original": { 772 | "id": "nixpkgs", 773 | "type": "indirect" 774 | } 775 | }, 776 | "nixpkgs_3": { 777 | "locked": { 778 | "lastModified": 1693780807, 779 | "narHash": "sha256-diV1X53HjSB3fIcDFieh9tGZkJ3vqJJQhTz89NbYw60=", 780 | "owner": "nixos", 781 | "repo": "nixpkgs", 782 | "rev": "84ef5335abf541d8148433489e0cf79affae3f89", 783 | "type": "github" 784 | }, 785 | "original": { 786 | "owner": "nixos", 787 | "ref": "nixpkgs-unstable", 788 | "repo": "nixpkgs", 789 | "type": "github" 790 | } 791 | }, 792 | "nixpkgs_4": { 793 | "locked": { 794 | "lastModified": 1696983906, 795 | "narHash": "sha256-L7GyeErguS7Pg4h8nK0wGlcUTbfUMDu+HMf1UcyP72k=", 796 | "owner": "NixOS", 797 | "repo": "nixpkgs", 798 | "rev": "bd1cde45c77891214131cbbea5b1203e485a9d51", 799 | "type": "github" 800 | }, 801 | "original": { 802 | "id": "nixpkgs", 803 | "ref": "nixos-23.05", 804 | "type": "indirect" 805 | } 806 | }, 807 | "nixpkgs_5": { 808 | "locked": { 809 | "lastModified": 1621552131, 810 | "narHash": "sha256-AD/AEXv+QOYAg0PIqMYv2nbGOGTIwfOGKtz3rE+y+Tc=", 811 | "owner": "NixOS", 812 | "repo": "nixpkgs", 813 | "rev": "d42cd445dde587e9a993cd9434cb43da07c4c5de", 814 | "type": "github" 815 | }, 816 | "original": { 817 | "id": "nixpkgs", 818 | "type": "indirect" 819 | } 820 | }, 821 | "nixpkgs_6": { 822 | "locked": { 823 | "lastModified": 1698675399, 824 | "narHash": "sha256-nj+LNEeVXGP31vxoL3x7HW7+oEiyoLVDqwMg30yFBMA=", 825 | "owner": "NixOS", 826 | "repo": "nixpkgs", 827 | "rev": "7378978469efa3b2b2f97d645a2a0b0e2447da2b", 828 | "type": "github" 829 | }, 830 | "original": { 831 | "owner": "NixOS", 832 | "repo": "nixpkgs", 833 | "type": "github" 834 | } 835 | }, 836 | "nixpkgs_7": { 837 | "locked": { 838 | "lastModified": 1723758588, 839 | "narHash": "sha256-xR37evwobI8AYc5bd+woU0bmRyVmAy+tWE4sv7TAiTI=", 840 | "owner": "nixos", 841 | "repo": "nixpkgs", 842 | "rev": "04e8554b1264d1eb3422104cf5bf28b04fc81d4d", 843 | "type": "github" 844 | }, 845 | "original": { 846 | "owner": "nixos", 847 | "repo": "nixpkgs", 848 | "type": "github" 849 | } 850 | }, 851 | "poetry2nix": { 852 | "inputs": { 853 | "flake-utils": "flake-utils_6", 854 | "nix-github-actions": "nix-github-actions", 855 | "nixpkgs": [ 856 | "kontrol", 857 | "kevm", 858 | "pyk", 859 | "nixpkgs" 860 | ], 861 | "systems": "systems_5", 862 | "treefmt-nix": "treefmt-nix" 863 | }, 864 | "locked": { 865 | "lastModified": 1698640399, 866 | "narHash": "sha256-mXzyx79/iFLZ0UDuSkqgFfejYRcSJfsCnJ9WlMusaI0=", 867 | "owner": "nix-community", 868 | "repo": "poetry2nix", 869 | "rev": "626111646fe236cb1ddc8191a48c75e072a82b7c", 870 | "type": "github" 871 | }, 872 | "original": { 873 | "owner": "nix-community", 874 | "repo": "poetry2nix", 875 | "rev": "626111646fe236cb1ddc8191a48c75e072a82b7c", 876 | "type": "github" 877 | } 878 | }, 879 | "pybind11-src": { 880 | "flake": false, 881 | "locked": { 882 | "lastModified": 1657936673, 883 | "narHash": "sha256-/X8DZPFsNrKGbhjZ1GFOj17/NU6p4R+saCW3pLKVNeA=", 884 | "owner": "pybind", 885 | "repo": "pybind11", 886 | "rev": "0ba639d6177659c5dc2955ac06ad7b5b0d22e05c", 887 | "type": "github" 888 | }, 889 | "original": { 890 | "owner": "pybind", 891 | "repo": "pybind11", 892 | "rev": "0ba639d6177659c5dc2955ac06ad7b5b0d22e05c", 893 | "type": "github" 894 | } 895 | }, 896 | "pyk": { 897 | "inputs": { 898 | "flake-utils": [ 899 | "kontrol", 900 | "kevm", 901 | "pyk", 902 | "poetry2nix", 903 | "flake-utils" 904 | ], 905 | "nixpkgs": "nixpkgs_6", 906 | "poetry2nix": "poetry2nix" 907 | }, 908 | "locked": { 909 | "lastModified": 1704997708, 910 | "narHash": "sha256-gWlflT/HiLRk88Ht9+iJ/DgSv0Br3bDXv40my/NqAkQ=", 911 | "owner": "runtimeverification", 912 | "repo": "pyk", 913 | "rev": "e47333fd09cac67a69c074a302e890466e88b964", 914 | "type": "github" 915 | }, 916 | "original": { 917 | "owner": "runtimeverification", 918 | "ref": "v0.1.572", 919 | "repo": "pyk", 920 | "type": "github" 921 | } 922 | }, 923 | "rapidjson-src": { 924 | "flake": false, 925 | "locked": { 926 | "lastModified": 1472111945, 927 | "narHash": "sha256-SxUXSOQDZ0/3zlFI4R84J56/1fkw2jhge4mexNF6Pco=", 928 | "owner": "Tencent", 929 | "repo": "rapidjson", 930 | "rev": "f54b0e47a08782a6131cc3d60f94d038fa6e0a51", 931 | "type": "github" 932 | }, 933 | "original": { 934 | "owner": "Tencent", 935 | "repo": "rapidjson", 936 | "rev": "f54b0e47a08782a6131cc3d60f94d038fa6e0a51", 937 | "type": "github" 938 | } 939 | }, 940 | "root": { 941 | "inputs": { 942 | "flake-utils": "flake-utils", 943 | "foundry": "foundry", 944 | "halmos-src": "halmos-src", 945 | "hevm": "hevm", 946 | "kontrol": "kontrol", 947 | "nixpkgs": "nixpkgs_7", 948 | "runlim-src": "runlim-src" 949 | } 950 | }, 951 | "runlim-src": { 952 | "flake": false, 953 | "locked": { 954 | "lastModified": 1688487804, 955 | "narHash": "sha256-HCaUP71ymtG1sRs6JWWvlJhYKECo/gcj2qBRU31WJxc=", 956 | "owner": "msooseth", 957 | "repo": "runlim", 958 | "rev": "f1b263bc22b6d89e68dbb26bf3ea3508d74154ef", 959 | "type": "github" 960 | }, 961 | "original": { 962 | "owner": "msooseth", 963 | "repo": "runlim", 964 | "type": "github" 965 | } 966 | }, 967 | "rv-utils": { 968 | "locked": { 969 | "lastModified": 1659349707, 970 | "narHash": "sha256-+RwJvYwRS4In+pl8R5Uz+R/yZ5yO5SAa7X6UR+eSC2U=", 971 | "owner": "runtimeverification", 972 | "repo": "rv-nix-tools", 973 | "rev": "7026604726c5247ceb6e7a1a7532302a58e7e2bf", 974 | "type": "github" 975 | }, 976 | "original": { 977 | "owner": "runtimeverification", 978 | "repo": "rv-nix-tools", 979 | "type": "github" 980 | } 981 | }, 982 | "secp256k1": { 983 | "flake": false, 984 | "locked": { 985 | "lastModified": 1502408521, 986 | "narHash": "sha256-PyqNZGER9VypH35S/aU4EBeepieI3BGXrYsJ141os24=", 987 | "owner": "bitcoin-core", 988 | "repo": "secp256k1", 989 | "rev": "f532bdc9f77f7bbf7e93faabfbe9c483f0a9f75f", 990 | "type": "github" 991 | }, 992 | "original": { 993 | "owner": "bitcoin-core", 994 | "repo": "secp256k1", 995 | "rev": "f532bdc9f77f7bbf7e93faabfbe9c483f0a9f75f", 996 | "type": "github" 997 | } 998 | }, 999 | "solc": { 1000 | "inputs": { 1001 | "flake-utils": [ 1002 | "kontrol", 1003 | "flake-utils" 1004 | ], 1005 | "nixpkgs": [ 1006 | "kontrol", 1007 | "nixpkgs" 1008 | ] 1009 | }, 1010 | "locked": { 1011 | "lastModified": 1700417764, 1012 | "narHash": "sha256-ssdwqKWkYUd/Nr6P9veR4D/PrtlwGJkPoUQoEgVJVpo=", 1013 | "owner": "hellwolf", 1014 | "repo": "solc.nix", 1015 | "rev": "80d2e38e98e589872b0dc3770f838c4be847305e", 1016 | "type": "github" 1017 | }, 1018 | "original": { 1019 | "owner": "hellwolf", 1020 | "repo": "solc.nix", 1021 | "type": "github" 1022 | } 1023 | }, 1024 | "solidity": { 1025 | "flake": false, 1026 | "locked": { 1027 | "lastModified": 1670421565, 1028 | "narHash": "sha256-VCZDKQHnMsXu+ucsT5BLPYQoZ2FYjpyGbDVoDYjr1W8=", 1029 | "owner": "ethereum", 1030 | "repo": "solidity", 1031 | "rev": "1c8745c54a239d20b6fb0f79a8bd2628d779b27e", 1032 | "type": "github" 1033 | }, 1034 | "original": { 1035 | "owner": "ethereum", 1036 | "repo": "solidity", 1037 | "rev": "1c8745c54a239d20b6fb0f79a8bd2628d779b27e", 1038 | "type": "github" 1039 | } 1040 | }, 1041 | "stacklock2nix": { 1042 | "locked": { 1043 | "lastModified": 1700633677, 1044 | "narHash": "sha256-ATrA3tZZYo9aj9IAZZNqyvtkz4Ub1Q3q5OgADwxImTA=", 1045 | "owner": "cdepillabout", 1046 | "repo": "stacklock2nix", 1047 | "rev": "84694f48ddd8e49b96a02216ca2ab406fba25e65", 1048 | "type": "github" 1049 | }, 1050 | "original": { 1051 | "owner": "cdepillabout", 1052 | "repo": "stacklock2nix", 1053 | "type": "github" 1054 | } 1055 | }, 1056 | "systems": { 1057 | "locked": { 1058 | "lastModified": 1681028828, 1059 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 1060 | "owner": "nix-systems", 1061 | "repo": "default", 1062 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 1063 | "type": "github" 1064 | }, 1065 | "original": { 1066 | "owner": "nix-systems", 1067 | "repo": "default", 1068 | "type": "github" 1069 | } 1070 | }, 1071 | "systems_2": { 1072 | "locked": { 1073 | "lastModified": 1681028828, 1074 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 1075 | "owner": "nix-systems", 1076 | "repo": "default", 1077 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 1078 | "type": "github" 1079 | }, 1080 | "original": { 1081 | "owner": "nix-systems", 1082 | "repo": "default", 1083 | "type": "github" 1084 | } 1085 | }, 1086 | "systems_3": { 1087 | "locked": { 1088 | "lastModified": 1681028828, 1089 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 1090 | "owner": "nix-systems", 1091 | "repo": "default", 1092 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 1093 | "type": "github" 1094 | }, 1095 | "original": { 1096 | "owner": "nix-systems", 1097 | "repo": "default", 1098 | "type": "github" 1099 | } 1100 | }, 1101 | "systems_4": { 1102 | "locked": { 1103 | "lastModified": 1681028828, 1104 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 1105 | "owner": "nix-systems", 1106 | "repo": "default", 1107 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 1108 | "type": "github" 1109 | }, 1110 | "original": { 1111 | "owner": "nix-systems", 1112 | "repo": "default", 1113 | "type": "github" 1114 | } 1115 | }, 1116 | "systems_5": { 1117 | "locked": { 1118 | "lastModified": 1681028828, 1119 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 1120 | "owner": "nix-systems", 1121 | "repo": "default", 1122 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 1123 | "type": "github" 1124 | }, 1125 | "original": { 1126 | "id": "systems", 1127 | "type": "indirect" 1128 | } 1129 | }, 1130 | "treefmt-nix": { 1131 | "inputs": { 1132 | "nixpkgs": [ 1133 | "kontrol", 1134 | "kevm", 1135 | "pyk", 1136 | "poetry2nix", 1137 | "nixpkgs" 1138 | ] 1139 | }, 1140 | "locked": { 1141 | "lastModified": 1697388351, 1142 | "narHash": "sha256-63N2eBpKaziIy4R44vjpUu8Nz5fCJY7okKrkixvDQmY=", 1143 | "owner": "numtide", 1144 | "repo": "treefmt-nix", 1145 | "rev": "aae39f64f5ecbe89792d05eacea5cb241891292a", 1146 | "type": "github" 1147 | }, 1148 | "original": { 1149 | "owner": "numtide", 1150 | "repo": "treefmt-nix", 1151 | "type": "github" 1152 | } 1153 | }, 1154 | "utils": { 1155 | "locked": { 1156 | "lastModified": 1620759905, 1157 | "narHash": "sha256-WiyWawrgmyN0EdmiHyG2V+fqReiVi8bM9cRdMaKQOFg=", 1158 | "owner": "numtide", 1159 | "repo": "flake-utils", 1160 | "rev": "b543720b25df6ffdfcf9227afafc5b8c1fabfae8", 1161 | "type": "github" 1162 | }, 1163 | "original": { 1164 | "owner": "numtide", 1165 | "repo": "flake-utils", 1166 | "type": "github" 1167 | } 1168 | }, 1169 | "xbyak": { 1170 | "flake": false, 1171 | "locked": { 1172 | "lastModified": 1518572786, 1173 | "narHash": "sha256-jqDSNDcqK7010ig941hrJFkv1X7MbgXmhPOOE9wHmho=", 1174 | "owner": "herumi", 1175 | "repo": "xbyak", 1176 | "rev": "f0a8f7faa27121f28186c2a7f4222a9fc66c283d", 1177 | "type": "github" 1178 | }, 1179 | "original": { 1180 | "owner": "herumi", 1181 | "repo": "xbyak", 1182 | "rev": "f0a8f7faa27121f28186c2a7f4222a9fc66c283d", 1183 | "type": "github" 1184 | } 1185 | }, 1186 | "z3": { 1187 | "flake": false, 1188 | "locked": { 1189 | "lastModified": 1674011426, 1190 | "narHash": "sha256-7cuUf29TMpX62PwO1ab3ZuzmzlcrRjTKB1CyXnYgYus=", 1191 | "owner": "Z3Prover", 1192 | "repo": "z3", 1193 | "rev": "3012293c35eadbfd73e5b94adbe50b0cc44ffb83", 1194 | "type": "github" 1195 | }, 1196 | "original": { 1197 | "owner": "Z3Prover", 1198 | "ref": "z3-4.12.1", 1199 | "repo": "z3", 1200 | "type": "github" 1201 | } 1202 | } 1203 | }, 1204 | "root": "root", 1205 | "version": 7 1206 | } 1207 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Eth SC Benchmarks"; 3 | 4 | inputs = { 5 | flake-utils.url = "github:numtide/flake-utils"; 6 | nixpkgs.url = "github:nixos/nixpkgs"; 7 | 8 | # tools 9 | hevm.url = "github:ethereum/hevm/bitwuzla-main"; 10 | kontrol.url = "github:runtimeverification/kontrol"; 11 | foundry.url = "github:shazow/foundry.nix/monthly"; 12 | halmos-src = { url = "github:a16z/halmos"; flake = false; }; 13 | runlim-src = { url = "github:msooseth/runlim"; flake = false; }; 14 | }; 15 | 16 | outputs = { self, nixpkgs, flake-utils, hevm, kontrol, foundry, halmos-src, runlim-src }: 17 | flake-utils.lib.eachDefaultSystem (system: 18 | let 19 | pkgs = nixpkgs.legacyPackages.${system}; 20 | runlim = pkgs.stdenv.mkDerivation { 21 | pname = "runlim"; 22 | version = "1.0"; 23 | configurePhase = '' 24 | mkdir -p $out/bin 25 | ./configure.sh --prefix=$out/bin 26 | ''; 27 | src = runlim-src; 28 | }; 29 | halmos = pkgs.python3.pkgs.buildPythonApplication rec { 30 | pname = "halmos"; 31 | version = "0.0.0"; 32 | src = halmos-src; 33 | format = "pyproject"; 34 | doCheck = false; 35 | postPatch = '' 36 | # Use upstream z3 implementation 37 | sed -i '/^.*"z3-solver==.*",$/d' "pyproject.toml" 38 | ''; 39 | buildInputs = with pkgs.python3.pkgs; [ setuptools ]; 40 | propagatedBuildInputs = with pkgs.python3.pkgs; [ setuptools setuptools_scm z3 sortedcontainers toml ]; 41 | }; 42 | in rec { 43 | packages.default = halmos; 44 | devShells.default = pkgs.mkShell { 45 | DAPP_SOLC="${pkgs.solc}/bin/solc"; 46 | packages = [ 47 | # tools 48 | halmos 49 | hevm.packages.${system}.default 50 | kontrol.packages.${system}.default 51 | foundry.defaultPackage.${system} 52 | pkgs.solc 53 | 54 | # python stuff 55 | pkgs.black 56 | pkgs.ruff 57 | pkgs.python3 58 | 59 | # shell script deps 60 | pkgs.jq 61 | pkgs.sqlite-interactive 62 | pkgs.gnuplot 63 | pkgs.time 64 | runlim 65 | ]; 66 | }; 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 7 | -------------------------------------------------------------------------------- /gen_graphs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sqlite3 5 | import optparse 6 | import re 7 | 8 | global opts 9 | opts : optparse.Values 10 | 11 | def unlink(fname): 12 | if not opts.no_del: 13 | os.unlink(fname) 14 | 15 | 16 | # Converts file to ascending space-delimited file that shows 17 | # the number of problems solved and the timeout it took to solve the next 18 | # file. Returns the number of problems solved in total 19 | # CDF stands for Cumulative Distribution Function 20 | def convert_to_cdf(fname: str, fname2: str) -> int: 21 | f = open(fname, "r") 22 | f2 = open(fname2, "w") 23 | text = f.read() 24 | mylines = text.splitlines() 25 | time = [] 26 | for line in mylines: 27 | time.append(float(line.split()[0])) 28 | 29 | lastnum = -1 30 | for a in range(0, 3600, 1): 31 | num = 0 32 | for t in time: 33 | if (t < a): 34 | num += 1 35 | 36 | if (lastnum != num): 37 | f2.write("%d \t%d\n" % (num, a)) 38 | lastnum = num 39 | f.close() 40 | f2.close() 41 | return len(mylines) 42 | 43 | 44 | # Get all solvers in the DB 45 | def get_solvers() -> list[str]: 46 | ret = [] 47 | with sqlite3.connect("results.db") as con: 48 | cur = con.cursor() 49 | res = cur.execute("SELECT solver FROM results group by solver") 50 | for a in res: 51 | ret.append(a[0]) 52 | return ret 53 | 54 | 55 | # generates files for GNU to plot for each solver 56 | def gen_cdf_files() -> list[tuple[str, str, int]]: 57 | ret = [] 58 | solvers = get_solvers() 59 | print("Solvers: ", solvers) 60 | for solver in solvers: 61 | fname_csv = "graphs/run-"+solver+".csv" 62 | fname_csv_gen = "gencsv.sqlite" 63 | with open(fname_csv_gen, "w") as f: 64 | f.write(".headers off\n") 65 | f.write(".mode csv\n") 66 | f.write(".output "+fname_csv+"\n") 67 | f.write("select t from results where solver='{solver}'\n and result!='unknown'".format(solver=solver)) 68 | os.system("sqlite3 results.db < {fname_csv_gen}".format(fname_csv_gen=fname_csv_gen)) 69 | unlink(fname_csv_gen) 70 | 71 | fname_cdf = fname_csv + ".gnuplotdata" 72 | num_solved = convert_to_cdf(fname_csv, fname_cdf) 73 | unlink(fname_csv) 74 | ret.append([fname_cdf, solver, num_solved]) 75 | return ret 76 | 77 | 78 | # Generates graphs with 2 solvers on X/Y axis and the dots representing problems that were solved 79 | # by the different solvers. 80 | def gen_comparative_graphs() -> None: 81 | def get_timeout(solver, solver2) -> int: 82 | with sqlite3.connect("results.db") as cur: 83 | ret = cur.execute(""" 84 | select tout 85 | from results 86 | where solver='{solver}' or solver='{solver2}' 87 | group by tout""".format(solver=solver, solver2=solver2)) 88 | tout = None 89 | for line in ret: 90 | if tout is None: 91 | tout = int(line[0]) 92 | else: 93 | print("Error, the two solvers, '{solver}' and '{solver2}' are incomparable".format( 94 | solver=solver, solver2=solver2)) 95 | print(" --> they were run with different timeouts") 96 | exit(-1) 97 | if tout is None: 98 | print("Error, timeout couldn't be found for solvers '{solver}' and '{solver2}'".format( 99 | solver=solver, solver2=solver2)) 100 | print(" ---> were they not run?") 101 | exit(-1) 102 | return tout 103 | def genplot(t : str, solver:str, solver2:str) -> str: 104 | timeout = get_timeout(solver, solver2) 105 | fname = solver+"-vs-"+solver2+"." + t 106 | with open(fname_gnuplot, "a") as f: 107 | if t == "eps": 108 | if opts.pretty_graphs: 109 | f.write("set term postscript eps color lw 1 \"Helvetica\" 16 size 3,2\n") 110 | else: 111 | f.write("set term postscript eps color lw 1 \"Helvetica\" 8 size 6,4\n") 112 | elif t == "png": 113 | f.write("set term png size 600,400\n") 114 | else: 115 | assert False 116 | 117 | f.write("set output \"graphs/"+fname+"\"\n") 118 | f.write("set notitle\n") 119 | f.write("set nokey\n") 120 | f.write("set logscale x\n") 121 | f.write("set logscale y\n") 122 | if opts.pretty_graphs: 123 | solver = re.sub(r"-tstamp.*", "", solver) 124 | solver2 = re.sub(r"-tstamp.*", "", solver2) 125 | f.write("set xlabel \""+solver+"\"\n") 126 | f.write("set ylabel \""+solver2+"\"\n") 127 | f.write("f(x) = x\n") 128 | f.write("plot[0.001:{tout}][0.001:{tout}] \\\n".format(tout = timeout)) 129 | f.write("\""+fname_gnuplot_data+"\" u 1:2 with points pt 9\\\n") 130 | f.write(",f(x) with lines ls 2 title \"y=x\"\n") 131 | f.write("\n") 132 | return fname 133 | 134 | solvers = get_solvers() 135 | for solver in solvers: 136 | for solver2 in solvers: 137 | if solver == solver2: 138 | continue 139 | # create data file 140 | with sqlite3.connect("results.db") as cur: 141 | ret = cur.execute(""" 142 | select 143 | (case when a.result=='unknown' then a.tout else a.t end), 144 | (case when b.result=='unknown' then b.tout else b.t end), 145 | a.name 146 | from results as a, results as b 147 | where 148 | a.solver='{solver}' and b.solver='{solver2}' and a.name=b.name""".format( 149 | solver=solver, solver2=solver2)) 150 | fname_gnuplot_data = "graphs/compare-"+solver+"-"+solver2+".gnuplotdata" 151 | with open(fname_gnuplot_data, "w") as f: 152 | for line in ret: 153 | solver1_t = line[0] 154 | solver2_t = line[1] 155 | name = line[2] 156 | f.write("%f %f %s\n" % (solver1_t, solver2_t, name)) 157 | 158 | # generate plot 159 | fname_gnuplot = "compare-{solver}-vs-{solver2}.gnuplot".format( 160 | solver=solver, solver2=solver2) 161 | os.system("rm -f \"{fname}\"".format(fname=fname_gnuplot)) 162 | if opts.pretty_graphs: 163 | todo = ["eps"] 164 | else: 165 | todo = ["eps", "png"] 166 | for t in todo: 167 | name = genplot(t, solver, solver2) 168 | print("Generating graph: graphs/{name}".format(name=name)) 169 | os.system("gnuplot "+fname_gnuplot) 170 | unlink(fname_gnuplot) 171 | 172 | # delete data file 173 | unlink(fname_gnuplot_data) 174 | 175 | 176 | # Generates a Cumulative Distribution Function (CDF) from the data 177 | # See: https://online.stat.psu.edu/stat414/lesson/14/14.2 178 | def gen_cdf_graph() -> None: 179 | cdf_files = gen_cdf_files() 180 | fname_gnuplot = "cdf.gnuplot" 181 | os.system("rm -f \"{fname}\"".format(fname=fname_gnuplot)) 182 | if opts.pretty_graphs: 183 | todo = ["eps"] 184 | else: 185 | todo = ["eps", "png"] 186 | 187 | for ext in todo: 188 | with open(fname_gnuplot, "a") as f: 189 | if ext == "eps": 190 | if opts.pretty_graphs: 191 | f.write("set term postscript eps color lw 1 \"Helvetica\" 13 size 4,2\n") 192 | else: 193 | f.write("set term postscript eps color lw 1 \"Helvetica\" 8 size 4,4\n") 194 | elif ext == "png": 195 | f.write("set term png size 800,600\n") 196 | else: 197 | assert False 198 | 199 | f.write("set output \"graphs/cdf.{ext}\"\n".format(ext=ext)) 200 | f.write("set title \"Solvers\"\n") 201 | f.write("set notitle\n") 202 | f.write("set key bottom right\n") 203 | f.write("unset logscale x\n") 204 | f.write("unset logscale y\n") 205 | f.write("set ylabel \"Problems solved\"\n") 206 | f.write("set xlabel \"Wallclock Time (s)\"\n") 207 | f.write("plot \\\n") 208 | towrite = "" 209 | for fname, solver, _ in cdf_files: 210 | if opts.pretty_graphs: 211 | solver = re.sub(r"-tstamp.*", "", solver) 212 | towrite += "\""+fname+"\" u 2:1 with linespoints title \""+solver+"\"" 213 | towrite += ",\\\n" 214 | towrite = towrite[:(len(towrite)-4)] 215 | f.write(towrite) 216 | f.write("\n") 217 | 218 | os.system("gnuplot "+fname_gnuplot) 219 | unlink(fname_gnuplot) 220 | for fname, _, _ in cdf_files: 221 | unlink(fname) 222 | 223 | print("graph generated: graphs/cdf.eps") 224 | print("graph generated: graphs/cdf.png") 225 | 226 | 227 | def check_all_same_tout() -> None: 228 | with sqlite3.connect("results.db") as cur: 229 | ret = cur.execute(""" 230 | select tout 231 | from results 232 | group by tout""") 233 | touts = [] 234 | for line in ret: 235 | touts.append(line[0]) 236 | 237 | if len(touts) > 1: 238 | print("ERROR. Some systems were ran with differing timeouts: ") 239 | for t in touts: 240 | print("timout observed: ", t) 241 | print("You must delete the results.db database and run all with the same timeouts") 242 | exit(-1) 243 | if len(touts) == 0: 244 | print("ERROR: no data in database!") 245 | exit(-1) 246 | 247 | def gen_boxgraphs() -> None: 248 | all_instances = [] 249 | with sqlite3.connect("results.db") as cur: 250 | instances = cur.execute("select name from results group by name") 251 | for n in instances: 252 | all_instances.append(n[0]) 253 | 254 | all_solvers = [] 255 | with sqlite3.connect("results.db") as cur: 256 | solvers = cur.execute("select solver from results group by solver") 257 | for n in solvers: 258 | all_solvers.append(n[0]) 259 | 260 | # generate data 261 | fname_boxdata = "boxdata.dat" 262 | tout = None 263 | with open(fname_boxdata, "w") as f: 264 | with sqlite3.connect("results.db") as cur: 265 | solve_time = {} 266 | for i in range(len(all_instances)): 267 | instance = all_instances[i] 268 | ret = cur.execute(""" 269 | select name, solver, (case when result=='unknown' then tout else t end),tout 270 | from results 271 | where name='{instance}'""".format(instance=instance)) 272 | for l in ret: 273 | assert instance == l[0] 274 | solver = l[1] 275 | t = l[2] 276 | tout = l[3] 277 | t = min(t, tout) 278 | solve_time[solver] = t 279 | instance_clean = os.path.basename(instance).replace("_", "\\\\\\_") 280 | f.write("{i} {instance}".format(instance=instance_clean, i=i+1)) 281 | for solver in all_solvers: 282 | assert tout is not None 283 | if solver not in solve_time: 284 | print("ERROR, solver '{solver}' was not run on instance '{instance}'".format(instance=instance, solver=solver)) 285 | exit(-1) 286 | f.write(" {t}".format(t=solve_time[solver])) 287 | f.write("\n") 288 | 289 | # generate gnuplot file 290 | fname_gnuplot = "boxplot.gnuplot" 291 | w = 0.1 292 | with open(fname_gnuplot, "w") as f: 293 | for t in ["eps", "png"]: 294 | if t == "eps": 295 | f.write("set term postscript eps color lw 1 \"Helvetica\" 6 size 8,3\n") 296 | elif t == "png": 297 | f.write("set term pngcairo font \"Arial,9\" size 1800,900\n") 298 | f.write("set output \"graphs/boxchart.{t}\"\n".format(t=t)) 299 | print("Generating graphs/boxchart.{t}".format(t=t)) 300 | f.write("set boxwidth {w}\n".format(w=str(w))) 301 | f.write("set style fill solid\n") 302 | f.write("set xtics rotate by -45\n") 303 | f.write("set key outside bottom right\n") 304 | f.write("set notitle\n") 305 | f.write("plot [0:] \\\n") 306 | half = round(len(all_solvers)/2.0) 307 | for i in range(len(all_solvers)): 308 | solver = all_solvers[i] 309 | if i == 0: 310 | xtic = ":xtic(2)" 311 | else: 312 | xtic = "" 313 | if i <= len(all_solvers)/2: 314 | f.write("\"{fname_boxdata}\" using ($1-{offs}):{at}{xtic} with boxes t \"{solver}\"".format( 315 | fname_boxdata=fname_boxdata, solver=solver, offs = (0.1*(half-i)), at=i+3, xtic=xtic)) 316 | else: 317 | f.write("\"{fname_boxdata}\" using ($1+{offs}):{at}{xtic} with boxes t \"{solver}\"".format( 318 | fname_boxdata=fname_boxdata, solver=solver, offs = (0.1*(i-half)), at=i+3, xtic=xtic)) 319 | 320 | if i < len(all_solvers)-1: 321 | f.write(", \\\n") 322 | f.write("\n") 323 | os.system("gnuplot "+fname_gnuplot) 324 | unlink(fname_gnuplot) 325 | unlink(fname_boxdata) 326 | 327 | 328 | # Set up options for main 329 | def set_up_parser() -> optparse.OptionParser: 330 | usage = "usage: %prog [options]" 331 | desc = """Generate all graphs 332 | """ 333 | 334 | parser = optparse.OptionParser(usage=usage, description=desc) 335 | parser.add_option("--verbose", "-v", action="store_true", default=False, 336 | dest="verbose", help="More verbose output. Default: %default") 337 | parser.add_option("--nodel", action="store_true", default=False, 338 | dest="no_del", help="Don't delete intermediate files. Allows you to run your own graphing tools on the raw datafiles, or edit the graphviz files to match your requirements") 339 | parser.add_option("--box", action="store_true", default=False, 340 | dest="box_only", help="Only generate box graph") 341 | parser.add_option("--cdf", action="store_true", default=False, 342 | dest="cdf_only", help="Only generate CDF graph") 343 | parser.add_option("--comp", action="store_true", default=False, 344 | dest="comp_only", help="Only generate comparative graph(s)") 345 | parser.add_option("--pretty", action="store_true", default=False, 346 | dest="pretty_graphs", help="Generate pretty, less cluttered graph(s)") 347 | 348 | return parser 349 | 350 | 351 | def main() -> None: 352 | try: 353 | os.mkdir("graphs") 354 | except FileExistsError: 355 | pass 356 | 357 | parser = set_up_parser() 358 | global opts 359 | (opts, _) = parser.parse_args() 360 | only_some : bool = opts.cdf_only or opts.box_only or opts.comp_only 361 | 362 | check_all_same_tout() 363 | if not only_some or opts.cdf_only: 364 | gen_cdf_graph() 365 | if not only_some or opts.box_only: 366 | gen_boxgraphs() 367 | if not only_some or opts.comp_only: 368 | gen_comparative_graphs() 369 | 370 | 371 | if __name__ == "__main__": 372 | main() 373 | -------------------------------------------------------------------------------- /src/common/auth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GNU-3 2 | // This program is free software: you can redistribute it and/or modify 3 | // it under the terms of the GNU General Public License as published by 4 | // the Free Software Foundation, either version 3 of the License, or 5 | // (at your option) any later version. 6 | 7 | // This program is distributed in the hope that it will be useful, 8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | // GNU General Public License for more details. 11 | 12 | // You should have received a copy of the GNU General Public License 13 | // along with this program. If not, see . 14 | 15 | pragma solidity >=0.4.23; 16 | 17 | interface DSAuthority { 18 | function canCall( 19 | address src, address dst, bytes4 sig 20 | ) external view returns (bool); 21 | } 22 | 23 | contract DSAuthEvents { 24 | event LogSetAuthority (address indexed authority); 25 | event LogSetOwner (address indexed owner); 26 | } 27 | 28 | contract DSAuth is DSAuthEvents { 29 | DSAuthority public authority; 30 | address public owner; 31 | 32 | constructor() { 33 | owner = msg.sender; 34 | emit LogSetOwner(msg.sender); 35 | } 36 | 37 | function setOwner(address owner_) 38 | public 39 | auth 40 | { 41 | owner = owner_; 42 | emit LogSetOwner(owner); 43 | } 44 | 45 | function setAuthority(DSAuthority authority_) 46 | public 47 | auth 48 | { 49 | authority = authority_; 50 | emit LogSetAuthority(address(authority)); 51 | } 52 | 53 | modifier auth { 54 | require(isAuthorized(msg.sender, msg.sig), "ds-auth-unauthorized"); 55 | _; 56 | } 57 | 58 | function isAuthorized(address src, bytes4 sig) internal view returns (bool) { 59 | if (src == address(this)) { 60 | return true; 61 | } else if (src == owner) { 62 | return true; 63 | } else if (authority == DSAuthority(address(0))) { 64 | return false; 65 | } else { 66 | return authority.canCall(src, address(this), sig); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/common/erc20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.19; 2 | 3 | // modified from solmate erc20 4 | // https://github.com/transmissions11/solmate/blob/c2594bf4635ad773a8f4763e20b7e79582e41535/src/tokens/ERC20.sol 5 | 6 | /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. 7 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) 8 | /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) 9 | /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. 10 | contract ERC20 { 11 | 12 | // --- events --- 13 | 14 | event Transfer(address indexed from, address indexed to, uint256 amount); 15 | event Approval(address indexed owner, address indexed spender, uint256 amount); 16 | 17 | // --- metadata --- 18 | 19 | string public name; 20 | string public symbol; 21 | uint8 public immutable decimals; 22 | 23 | // --- erc20 data --- 24 | 25 | uint256 public totalSupply; 26 | mapping(address => uint256) public balanceOf; 27 | mapping(address => mapping(address => uint256)) public allowance; 28 | 29 | // --- admin --- 30 | 31 | address public owner; 32 | modifier auth { require(owner == msg.sender, "not-authorized"); _; } 33 | 34 | // --- init --- 35 | 36 | constructor( 37 | string memory _name, 38 | string memory _symbol, 39 | uint8 _decimals 40 | ) { 41 | name = _name; 42 | symbol = _symbol; 43 | decimals = _decimals; 44 | 45 | owner = msg.sender; 46 | } 47 | 48 | // --- erc20 logic --- 49 | 50 | function approve(address spender, uint256 amount) public returns (bool) { 51 | allowance[msg.sender][spender] = amount; 52 | 53 | emit Approval(msg.sender, spender, amount); 54 | 55 | return true; 56 | } 57 | 58 | function transfer(address to, uint256 amount) public returns (bool) { 59 | balanceOf[msg.sender] -= amount; 60 | 61 | // Cannot overflow because the sum of all user 62 | // balances can't exceed the max uint256 value. 63 | unchecked { 64 | balanceOf[to] += amount; 65 | } 66 | 67 | emit Transfer(msg.sender, to, amount); 68 | 69 | return true; 70 | } 71 | 72 | function transferFrom( 73 | address from, 74 | address to, 75 | uint256 amount 76 | ) public returns (bool) { 77 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 78 | 79 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 80 | 81 | balanceOf[from] -= amount; 82 | 83 | // Cannot overflow because the sum of all user 84 | // balances can't exceed the max uint256 value. 85 | unchecked { 86 | balanceOf[to] += amount; 87 | } 88 | 89 | emit Transfer(from, to, amount); 90 | 91 | return true; 92 | } 93 | 94 | // --- mint / burn logic --- 95 | 96 | function mint(address to, uint256 amount) public auth { 97 | totalSupply += amount; 98 | 99 | // Cannot overflow because the sum of all user 100 | // balances can't exceed the max uint256 value. 101 | unchecked { 102 | balanceOf[to] += amount; 103 | } 104 | 105 | emit Transfer(address(0), to, amount); 106 | } 107 | 108 | function burn(address from, uint256 amount) public auth { 109 | balanceOf[from] -= amount; 110 | 111 | // Cannot underflow because a user's balance 112 | // will never be larger than the total supply. 113 | unchecked { 114 | totalSupply -= amount; 115 | } 116 | 117 | emit Transfer(from, address(0), amount); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/safe/1tx-abstract/erc721A.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | import 'lib/ERC721A/contracts/ERC721A.sol'; 4 | 5 | contract ERC721ATest is ERC721A { 6 | 7 | constructor() ERC721A("TKN", "TKN") { } 8 | 9 | function isBurned(uint tokenId) public view returns (bool) { 10 | return _packedOwnershipOf(tokenId) & _BITMASK_BURNED != 0; 11 | } 12 | 13 | // 14 | // mint 15 | // 16 | 17 | function mint(address to, uint quantity) public { 18 | _mint(to, quantity); 19 | } 20 | 21 | function proveMintRequirements(address to, uint quantity) public { 22 | _mint(to, quantity); 23 | 24 | assert(to != address(0)); 25 | assert(quantity > 0); 26 | } 27 | 28 | function proveMintNextTokenIdUpdate(address to, uint quantity) public { 29 | uint oldNextTokenId = _nextTokenId(); 30 | require(oldNextTokenId <= type(uint96).max); // practical assumption needed for overflow/underflow not occurring 31 | 32 | mint(to, quantity); 33 | 34 | uint newNextTokenId = _nextTokenId(); 35 | 36 | assert(newNextTokenId >= oldNextTokenId); // ensuring no overflow 37 | assert(newNextTokenId == oldNextTokenId + quantity); 38 | } 39 | 40 | function proveMintBalanceUpdate(address to, uint quantity, uint tokenId) public { 41 | uint oldBalanceTo = balanceOf(to); 42 | require(oldBalanceTo <= type(uint64).max / 2); // practical assumption needed for balance staying within uint64 43 | 44 | mint(to, quantity); 45 | 46 | uint newBalanceTo = balanceOf(to); 47 | 48 | assert(newBalanceTo > oldBalanceTo); // ensuring no overflow 49 | assert(newBalanceTo == oldBalanceTo + quantity); 50 | } 51 | 52 | function proveMintOwnershipUpdate(address to, uint quantity, uint _newNextTokenId) public { 53 | uint oldNextTokenId = _nextTokenId(); 54 | require(oldNextTokenId <= type(uint96).max); // practical assumption needed for overflow/underflow not occurring 55 | 56 | // global invariant 57 | for (uint i = oldNextTokenId; i < _newNextTokenId; i++) { 58 | require(_packedOwnerships[i] == 0); // assumption for uninitialized mappings (i.e., no hash collision for hashed storage addresses) 59 | } 60 | 61 | mint(to, quantity); 62 | 63 | uint newNextTokenId = _nextTokenId(); 64 | require(_newNextTokenId == newNextTokenId); 65 | 66 | for (uint i = oldNextTokenId; i < newNextTokenId; i++) { 67 | assert(ownerOf(i) == to); 68 | assert(!isBurned(i)); 69 | } 70 | } 71 | 72 | function proveMintOtherBalancePreservation(address to, uint quantity, address others) public { 73 | require(others != to); // consider other addresses 74 | 75 | uint oldBalanceOther = balanceOf(others); 76 | 77 | mint(to, quantity); 78 | 79 | uint newBalanceOther = balanceOf(others); 80 | 81 | assert(newBalanceOther == oldBalanceOther); // the balance of other addresses never change 82 | } 83 | 84 | function proveMintOtherOwnershipPreservation(address to, uint quantity, uint existingTokenId) public { 85 | uint oldNextTokenId = _nextTokenId(); 86 | require(oldNextTokenId <= type(uint96).max); // practical assumption needed for overflow/underflow not occurring 87 | 88 | require(existingTokenId < oldNextTokenId); // consider other token ids 89 | 90 | address oldOwnerExisting = ownerOf(existingTokenId); 91 | bool oldBurned = isBurned(existingTokenId); 92 | 93 | mint(to, quantity); 94 | 95 | address newOwnerExisting = ownerOf(existingTokenId); 96 | bool newBurned = isBurned(existingTokenId); 97 | 98 | assert(newOwnerExisting == oldOwnerExisting); // the owner of other token ids never change 99 | assert(newBurned == oldBurned); 100 | } 101 | 102 | // 103 | // burn 104 | // 105 | 106 | // TODO: duplicate spec for both modes 107 | function burn(uint tokenId) public { 108 | //_burn(tokenId, true); 109 | _burn(tokenId, false); 110 | } 111 | 112 | function proveBurnRequirements(uint tokenId) public { 113 | // TODO: prove global invariant 114 | require(!(_packedOwnerships[tokenId] == 0) || !isBurned(tokenId)); 115 | require(!(_packedOwnerships[tokenId] != 0) || tokenId < _nextTokenId()); 116 | 117 | bool exist = _exists(tokenId); 118 | bool burned = isBurned(tokenId); 119 | 120 | address owner = ownerOf(tokenId); 121 | bool approved = msg.sender == _tokenApprovals[tokenId].value || isApprovedForAll(owner, msg.sender); 122 | 123 | _burn(tokenId, true); 124 | 125 | assert(exist); // it should have reverted if the token id does not exist 126 | assert(!burned); 127 | 128 | assert(msg.sender == owner || approved); 129 | 130 | assert(_tokenApprovals[tokenId].value == address(0)); // getApproved(tokenId) reverts here 131 | } 132 | 133 | function proveBurnNextTokenIdUnchanged(uint tokenId) public { 134 | uint oldNextTokenId = _nextTokenId(); 135 | 136 | burn(tokenId); 137 | 138 | uint newNextTokenId = _nextTokenId(); 139 | 140 | assert(newNextTokenId == oldNextTokenId); 141 | } 142 | 143 | function proveBurnBalanceUpdate(uint tokenId) public { 144 | address from = ownerOf(tokenId); 145 | uint oldBalanceFrom = balanceOf(from); 146 | 147 | // TODO: prove global invariant 148 | require(!(_packedOwnerships[tokenId] != 0) || tokenId < _nextTokenId()); 149 | require(!_exists(tokenId) || oldBalanceFrom > 0); 150 | 151 | burn(tokenId); 152 | 153 | uint newBalanceFrom = balanceOf(from); 154 | 155 | assert(newBalanceFrom < oldBalanceFrom); // ensuring no overflow 156 | assert(newBalanceFrom == oldBalanceFrom - 1); 157 | } 158 | 159 | function proveBurnOwnershipUpdate(uint tokenId) public { 160 | burn(tokenId); 161 | 162 | assert(!_exists(tokenId)); 163 | assert(_packedOwnerships[tokenId] & _BITMASK_BURNED != 0); // isBurned reverts here 164 | } 165 | 166 | function proveBurnOtherBalancePreservation(uint tokenId, address others) public { 167 | address from = ownerOf(tokenId); 168 | require(others != from); // consider other addresses 169 | 170 | uint oldBalanceOther = balanceOf(others); 171 | 172 | burn(tokenId); 173 | 174 | uint newBalanceOther = balanceOf(others); 175 | 176 | assert(newBalanceOther == oldBalanceOther); 177 | } 178 | 179 | function proveBurnOtherOwnershipPreservation(uint tokenId, uint otherTokenId) public { 180 | require(_nextTokenId() <= type(uint96).max); // practical assumption needed for avoiding overflow/underflow 181 | 182 | // TODO: prove global invariant 183 | // global invariant 184 | require(!(_packedOwnerships[tokenId] == 0) || _packedOwnershipOf(tokenId) & _BITMASK_NEXT_INITIALIZED == 0); 185 | require(!(_packedOwnerships[tokenId] != 0) || tokenId < _nextTokenId()); 186 | require(!(_packedOwnershipOf(tokenId) & _BITMASK_NEXT_INITIALIZED != 0) 187 | || !(tokenId + 1 < _nextTokenId()) 188 | || _packedOwnerships[tokenId + 1] != 0); 189 | 190 | require(otherTokenId != tokenId); // consider other token ids 191 | 192 | address oldOtherTokenOwner = ownerOf(otherTokenId); 193 | bool oldBurned = isBurned(otherTokenId); 194 | 195 | burn(tokenId); 196 | 197 | address newOtherTokenOwner = ownerOf(otherTokenId); 198 | bool newBurned = isBurned(otherTokenId); 199 | 200 | assert(newOtherTokenOwner == oldOtherTokenOwner); 201 | assert(newBurned == oldBurned); 202 | } 203 | 204 | // 205 | // transfer 206 | // 207 | 208 | // TODO: duplicate spec for safeTransferFrom 209 | function transfer(address from, address to, uint tokenId) public { 210 | transferFrom(from, to, tokenId); 211 | //safeTransferFrom(from, to, tokenId); 212 | //safeTransferFrom(from, to, tokenId, data); 213 | } 214 | 215 | function proveTransferRequirements(address from, address to, uint tokenId) public { 216 | // TODO: prove global invariant 217 | require(!(_packedOwnerships[tokenId] == 0) || !isBurned(tokenId)); 218 | require(!(_packedOwnerships[tokenId] != 0) || tokenId < _nextTokenId()); 219 | 220 | bool exist = _exists(tokenId); 221 | bool burned = isBurned(tokenId); 222 | 223 | address owner = ownerOf(tokenId); 224 | bool approved = msg.sender == _tokenApprovals[tokenId].value || isApprovedForAll(owner, msg.sender); 225 | 226 | transfer(from, to, tokenId); 227 | 228 | assert(exist); // it should have reverted if the token id does not exist 229 | assert(!burned); 230 | 231 | //assert(from != address(0)); // NOTE: ERC721A doesn't explicitly check this condition 232 | assert(to != address(0)); 233 | 234 | assert(from == owner); 235 | assert(msg.sender == owner || approved); 236 | 237 | assert(_tokenApprovals[tokenId].value == address(0)); 238 | } 239 | 240 | function proveTransferNextTokenIdUnchanged(address from, address to, uint tokenId) public { 241 | uint oldNextTokenId = _nextTokenId(); 242 | 243 | transfer(from, to, tokenId); 244 | 245 | uint newNextTokenId = _nextTokenId(); 246 | 247 | assert(newNextTokenId == oldNextTokenId); 248 | } 249 | 250 | function proveTransferBalanceUpdate(address from, address to, uint tokenId) public { 251 | require(from != to); // consider normal transfer case (see below for the self-transfer case) 252 | 253 | uint oldBalanceFrom = balanceOf(from); 254 | uint oldBalanceTo = balanceOf(to); 255 | 256 | require(oldBalanceTo <= type(uint64).max / 2); // practical assumption needed for balance staying within uint64 257 | 258 | // TODO: prove global invariant 259 | require(!(_packedOwnerships[tokenId] != 0) || tokenId < _nextTokenId()); 260 | require(!_exists(tokenId) || balanceOf(ownerOf(tokenId)) > 0); 261 | 262 | transfer(from, to, tokenId); 263 | 264 | uint newBalanceFrom = balanceOf(from); 265 | uint newBalanceTo = balanceOf(to); 266 | 267 | assert(newBalanceFrom < oldBalanceFrom); 268 | assert(newBalanceFrom == oldBalanceFrom - 1); 269 | 270 | assert(newBalanceTo > oldBalanceTo); 271 | assert(newBalanceTo == oldBalanceTo + 1); 272 | } 273 | 274 | function proveTransferBalanceUnchanged(address from, address to, uint tokenId) public { 275 | require(from == to); // consider self-transfer case 276 | mint(from, 1); 277 | 278 | uint oldBalance = balanceOf(from); // == balanceOf(to); 279 | 280 | transfer(from, to, tokenId); 281 | 282 | uint newBalance = balanceOf(from); // == balanceOf(to); 283 | 284 | assert(newBalance == oldBalance); 285 | } 286 | 287 | function proveTransferOwnershipUpdate(address from, address to, uint tokenId) public { 288 | transfer(from, to, tokenId); 289 | 290 | assert(ownerOf(tokenId) == to); 291 | assert(!isBurned(tokenId)); 292 | } 293 | 294 | function proveTransferOtherBalancePreservation(address from, address to, uint tokenId, address others) public { 295 | require(others != from); // consider other addresses 296 | require(others != to); 297 | 298 | uint oldBalanceOther = balanceOf(others); 299 | 300 | transfer(from, to, tokenId); 301 | 302 | uint newBalanceOther = balanceOf(others); 303 | 304 | assert(newBalanceOther == oldBalanceOther); 305 | } 306 | 307 | function proveTransferOtherOwnershipPreservation(address from, address to, uint tokenId, uint otherTokenId) public { 308 | require(_nextTokenId() <= type(uint96).max); // practical assumption needed for avoiding overflow/underflow 309 | 310 | // TODO: prove global invariant 311 | // global invariant 312 | require(!(_packedOwnerships[tokenId] == 0) || _packedOwnershipOf(tokenId) & _BITMASK_NEXT_INITIALIZED == 0); 313 | require(!(_packedOwnerships[tokenId] != 0) || tokenId < _nextTokenId()); 314 | require(!(_packedOwnershipOf(tokenId) & _BITMASK_NEXT_INITIALIZED != 0) 315 | || !(tokenId + 1 < _nextTokenId()) 316 | || _packedOwnerships[tokenId + 1] != 0); 317 | 318 | require(otherTokenId != tokenId); // consider other token ids 319 | 320 | address oldOtherTokenOwner = ownerOf(otherTokenId); 321 | bool oldBurned = isBurned(otherTokenId); 322 | 323 | transfer(from, to, tokenId); 324 | 325 | address newOtherTokenOwner = ownerOf(otherTokenId); 326 | bool newBurned = isBurned(otherTokenId); 327 | 328 | assert(newOtherTokenOwner == oldOtherTokenOwner); 329 | assert(newBurned == oldBurned); 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /src/safe/1tx-abstract/sym-storage-safe.sol: -------------------------------------------------------------------------------- 1 | contract SymStorageSafe { 2 | mapping (uint => uint) map; 3 | uint[] arr; 4 | 5 | function prove_mapping(uint u, uint v) public { 6 | require(u == v); 7 | assert(map[u] == map[v]); 8 | } 9 | 10 | function prove_array(uint i, uint j) public { 11 | require(i == j); 12 | assert(arr[i] == arr[j]); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/safe/ds-test/amm.sol: -------------------------------------------------------------------------------- 1 | import {ERC20} from "./erc20.sol"; 2 | import "forge-std/Test.sol"; 3 | 4 | function min(uint x, uint y) returns (uint) { 5 | return x < y ? x : y; 6 | } 7 | 8 | contract AMM is ERC20 { 9 | ERC20 token0; 10 | ERC20 token1; 11 | 12 | constructor(address _token0, address _token1) ERC20("lp-token", "LPT", 18) { 13 | token0 = ERC20(_token0); 14 | token1 = ERC20(_token1); 15 | } 16 | 17 | // join allows the caller to exchange amt0 and amt1 tokens for some amount 18 | // of pool shares. The exact amount of pool shares minted depends on the 19 | // state of the pool at the time of the call. 20 | function join(uint amt0, uint amt1) external { 21 | require(amt0 > 0 && amt1 > 0, "insufficient input amounts"); 22 | 23 | uint bal0 = token0.balanceOf(address(this)); 24 | uint bal1 = token1.balanceOf(address(this)); 25 | 26 | uint shares = totalSupply == 0 27 | ? min(amt0, amt1) 28 | : min((totalSupply * amt0) / bal0, 29 | (totalSupply * amt1) / bal1); 30 | 31 | balanceOf[msg.sender] += shares; 32 | totalSupply += shares; 33 | 34 | token0.transferFrom(msg.sender, address(this), amt0); 35 | token1.transferFrom(msg.sender, address(this), amt1); 36 | } 37 | 38 | // exit allows the caller to exchange shares pool shares for the 39 | // proportional amount of the underlying tokens. 40 | function exit(uint shares) external { 41 | uint amt0 = (token0.balanceOf(address(this)) * shares) / totalSupply; 42 | uint amt1 = (token1.balanceOf(address(this)) * shares) / totalSupply; 43 | 44 | balanceOf[msg.sender] -= shares; 45 | totalSupply -= shares; 46 | 47 | token0.transfer(msg.sender, amt0); 48 | token1.transfer(msg.sender, amt1); 49 | } 50 | 51 | // swap allows the caller to exchange amt of src for dst at a price given 52 | // by the constant product formula: x * y == k. 53 | function swap(address src, address dst, uint amt) external { 54 | require(src != dst, "no self swap"); 55 | require(src == address(token0) || src == address(token1), "src not in pair"); 56 | require(dst == address(token0) || dst == address(token1), "dst not in pair"); 57 | 58 | uint K = token0.balanceOf(address(this)) * token1.balanceOf(address(this)); 59 | 60 | ERC20(src).transferFrom(msg.sender, address(this), amt); 61 | 62 | uint out 63 | = ERC20(dst).balanceOf(address(this)) 64 | - (K / ERC20(src).balanceOf(address(this)) + 1); // rounding 65 | 66 | ERC20(dst).transfer(msg.sender, out); 67 | 68 | uint KPost = token0.balanceOf(address(this)) * token1.balanceOf(address(this)); 69 | assert(KPost >= K); 70 | } 71 | } 72 | 73 | contract AmmTest is Test { 74 | ERC20 token0; 75 | ERC20 token1; 76 | AMM amm; 77 | 78 | constructor() public { 79 | token0 = new ERC20("t0", "t0", 18); 80 | token1 = new ERC20("t1", "t1", 18); 81 | amm = new AMM(address(token0), address(token1)); 82 | } 83 | 84 | function prove_swap(bool direction, address usr, uint lp0, uint lp1, uint amt) public { 85 | require(usr != address(this)); 86 | address src = direction ? address(token0) : address(token1); 87 | address dst = direction ? address(token1) : address(token0); 88 | 89 | // we LP from the test contract 90 | token0.mint(address(this), lp0); 91 | token1.mint(address(this), lp1); 92 | token0.approve(address(amm), type(uint).max); 93 | token1.approve(address(amm), type(uint).max); 94 | amm.join(lp0, lp1); 95 | 96 | // give usr some tokens 97 | ERC20(src).mint(usr, amt); 98 | 99 | // approve amm for usr 100 | vm.prank(usr); 101 | token0.approve(address(amm), type(uint).max); 102 | vm.prank(usr); 103 | token1.approve(address(amm), type(uint).max); 104 | 105 | /// assertion in swap should never be violated 106 | vm.prank(usr); 107 | amm.swap(src, dst, amt); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/safe/ds-test/arith-safe.sol: -------------------------------------------------------------------------------- 1 | import {DSTest} from "ds-test/test.sol"; 2 | 3 | contract UncheckedAddMulProperties is DSTest { 4 | function prove_add_commutative(uint x, uint y) public { 5 | unchecked { 6 | assert(x + y == y + x); 7 | } 8 | } 9 | function prove_add_assoc(uint x, uint y, uint z) public { 10 | unchecked { 11 | assert(x + (y + z) == (x + y) + z); 12 | } 13 | } 14 | function prove_mul_commutative(uint x, uint y) public { 15 | unchecked { 16 | assert(x * y == y * x); 17 | } 18 | } 19 | function prove_mul_assoc(uint x, uint y, uint z) public { 20 | unchecked { 21 | assert(x * (y * z) == (x * y) * z); 22 | } 23 | } 24 | } 25 | 26 | contract UncheckedDivProperties is DSTest { 27 | function prove_div_ident(uint x) public pure { 28 | unchecked { 29 | assert(x / 1 == x); 30 | } 31 | } 32 | function prove_div_mul_inverse_rough(uint x, uint y) public pure { 33 | unchecked { 34 | assert((x / y) * y <= x); 35 | } 36 | } 37 | function prove_zero_div(uint256 val) public pure { 38 | uint out; 39 | assembly { 40 | out := div(0, val) 41 | } 42 | assert(out == 0); 43 | 44 | } 45 | // TODO: does this actually hold if we have overflow? 46 | function prove_div_mul_inverse_precise(uint x, uint y) public { 47 | unchecked { 48 | assert(x == ((x / y) * y) + (x % y)); 49 | } 50 | } 51 | } 52 | 53 | contract UnchekedSubProperties is DSTest { 54 | function prove_sub_inverse(uint x, uint y) public { 55 | unchecked { 56 | uint z = x + y; 57 | assert(z - y == x); 58 | assert(z - x == y); 59 | } 60 | } 61 | function prove_sub_ident(uint x) public { 62 | unchecked { 63 | assert(x - 0 == x); 64 | } 65 | } 66 | function prove_sub_neg(int x, int y) public { 67 | unchecked { 68 | assert(x - y == x + (- y)); 69 | } 70 | } 71 | } 72 | 73 | contract CheckedAddMulProperties is DSTest { 74 | function prove_add_commutative(uint x, uint y) public { 75 | assert(x + y == y + x); 76 | } 77 | function prove_add_assoc(uint x, uint y, uint z) public { 78 | assert(x + (y + z) == (x + y) + z); 79 | } 80 | function prove_mul_commutative(uint x, uint y) public { 81 | assert(x * y == y * x); 82 | } 83 | function prove_mul_assoc(uint x, uint y, uint z) public { 84 | assert(x * (y * z) == (x * y) * z); 85 | } 86 | function prove_add_mul_distributivity(uint x, uint y, uint z) public { 87 | assert(x * (y + z) == (x * y) + (x * z)); 88 | } 89 | } 90 | 91 | contract CheckedDivProperties is DSTest { 92 | function prove_div_ident(uint x) public { 93 | assert(x / 1 == x); 94 | } 95 | function prove_div_mul_inverse_rough(uint x, uint y) public { 96 | assert((x / y) * y <= x); 97 | } 98 | function prove_div_mul_inverse_precise(uint x, uint y) public { 99 | assert(x == ((x / y) * y) + (x % y)); 100 | } 101 | } 102 | 103 | contract CheckedSubProperties is DSTest { 104 | function prove_sub_inverse(uint x, uint y) public { 105 | uint z = x + y; 106 | assert(z - y == x); 107 | assert(z - x == y); 108 | } 109 | function prove_sub_ident(uint x) public { 110 | assert(x - 0 == x); 111 | } 112 | function prove_sub_neg(int x, int y) public { 113 | assert(x - y == x + (- y)); 114 | } 115 | function prove_sub_larger(int x, int y) public { 116 | require(x > y); 117 | assert(y - x < 0); 118 | } 119 | } 120 | 121 | contract ModProperties is DSTest { 122 | function prove_mod_range(uint x, uint y) public { 123 | uint res = x % y; 124 | assert(0 <= res); 125 | assert(res <= y - 1); 126 | } 127 | function prove_mod_periodicity(uint x, uint y, uint z) public { 128 | assert(x % y == (x + (z * y)) % y); 129 | } 130 | function prove_mod_add_comm(uint x, uint y, uint z) public { 131 | assert((x + z) % y == ((x % y) + (z % y)) % y); 132 | } 133 | function prove_mod_mul_comm(uint x, uint y, uint z) public { 134 | assert((x * z) % y == ((x % y) * (z % y)) % y); 135 | } 136 | function prove_mod_add_distr(uint x, uint y, uint z) public { 137 | assert((x + z) % y == ((x % y) + (z % y)) % y); 138 | } 139 | function prove_mod_mul_distr(uint x, uint y, uint z) public { 140 | assert((x * z) % y == ((x % y) * (z % y)) % y); 141 | } 142 | } 143 | 144 | contract AddModProperties is DSTest { 145 | function prove_addmod_range(uint a, uint b, uint N) public pure { 146 | require(N != 0, "N should be non-zero"); 147 | uint result = addmod(a, b, N); 148 | assert(result >= 0 && result < N); 149 | } 150 | 151 | function prove_addmod_comm(uint a, uint b, uint N) public pure { 152 | require(N != 0, "N should be non-zero"); 153 | assert(addmod(a, b, N) == addmod(b, a, N)); 154 | } 155 | 156 | function prove_addmod_assoc(uint a, uint b, uint c, uint N) public pure { 157 | require(N != 0, "N should be non-zero"); 158 | assert(addmod(a, addmod(b, c, N), N) == addmod(addmod(a, b, N), c, N)); 159 | } 160 | 161 | function prove_addmod_identity(uint a, uint N) public pure { 162 | require(N != 0, "N should be non-zero"); 163 | assert(addmod(a, 0, N) == a % N); 164 | } 165 | 166 | function prove_addmod_inverse(uint a, uint N) public pure { 167 | require(N != 0, "N should be non-zero"); 168 | uint inverse = N - (a % N); 169 | assert(addmod(a, inverse, N) == 0); 170 | } 171 | 172 | function prove_addmod_periodicity(uint a, uint b, uint k, uint N) public pure { 173 | require(N != 0, "N should be non-zero"); 174 | assert(addmod(a, b, N) == addmod(a, b + k * N, N)); 175 | } 176 | 177 | function prove_addmod_nonzero(uint a, uint b, uint N) public pure { 178 | require(N != 0, "N should be non-zero"); 179 | uint result = addmod(a, b, N); 180 | assert(result >= 0); 181 | } 182 | 183 | function prove_addmod_equiv(uint a, uint b, uint N) public pure { 184 | require(N != 0, "N should be non-zero"); 185 | uint result = addmod(a, b, N); 186 | assert(result == (a + b) % N); 187 | } 188 | 189 | function prove_addmod_no_overflow(uint8 a, uint8 b, uint8 c) external pure { 190 | require(a < 4); 191 | require(b < 4); 192 | require(c < 4); 193 | uint16 r1; 194 | uint16 r2; 195 | uint16 g2; 196 | assembly { 197 | r1 := add(a,b) 198 | r2 := mod(r1, c) 199 | g2 := addmod (a, b, c) 200 | } 201 | assert (r2 == g2); 202 | } 203 | } 204 | 205 | contract MulModProperties is DSTest { 206 | function prove_mulmod_range(uint a, uint b, uint N) public pure { 207 | require(N != 0, "N should be non-zero"); 208 | uint result = mulmod(a, b, N); 209 | assert(result >= 0 && result < N); 210 | } 211 | 212 | // Asserts commutativity of mulmod 213 | function prove_mulmod_commutivity(uint a, uint b, uint N) public pure { 214 | require(N != 0, "N should be non-zero"); 215 | assert(mulmod(a, b, N) == mulmod(b, a, N)); 216 | } 217 | 218 | function prove_mulmod_distributivity(uint a, uint b, uint c, uint N) public pure { 219 | require(N != 0, "N should be non-zero"); 220 | assert(mulmod(a, addmod(b, c, N), N) == addmod(mulmod(a, b, N), mulmod(a, c, N), N)); 221 | } 222 | 223 | function prove_mulmod_identity(uint a, uint N) public pure { 224 | require(N != 0, "N should be non-zero"); 225 | assert(mulmod(a, 1, N) == a % N); 226 | } 227 | 228 | function prove_mulmod_nonzero(uint a, uint b, uint N) public pure { 229 | require(N != 0, "N should be non-zero"); 230 | uint result = mulmod(a, b, N); 231 | assert(result >= 0); 232 | } 233 | 234 | function prove_mulmod_equiv(uint a, uint b, uint N) public pure { 235 | require(N != 0, "N should be non-zero"); 236 | uint result = mulmod(a, b, N); 237 | assert(result == (a * b) % N); 238 | } 239 | 240 | function prove_mulmod_no_overflow(uint8 a, uint8 b, uint8 c) external pure { 241 | require(a < 4); 242 | require(b < 4); 243 | require(c < 4); 244 | uint16 r1; 245 | uint16 r2; 246 | uint16 g2; 247 | assembly { 248 | r1 := mul(a,b) 249 | r2 := mod(r1, c) 250 | g2 := mulmod (a, b, c) 251 | } 252 | assert (r2 == g2); 253 | } 254 | } 255 | 256 | 257 | contract SignedDivisionProperties is DSTest { 258 | // helpers 259 | function sdiv(int a, int b) internal pure returns (int res) { 260 | assembly { res := sdiv(a, b) } 261 | } 262 | 263 | // properties 264 | function prove_divide_anything_by_zero(int a) public { 265 | assert(sdiv(a,0) == 0); 266 | } 267 | 268 | function prove_divide_zero_by_anything(int a) public { 269 | int result = sdiv(0, a); 270 | assert(result == 0); 271 | } 272 | 273 | function prove_divide_positive_by_positive(int a, int b) public { 274 | require(a > 0); 275 | require(b > 0); 276 | int result = sdiv(a, b); 277 | assert(result >= 0); 278 | } 279 | 280 | function prove_divide_positive_by_negative(int a, int b) public { 281 | require(a > 0); 282 | require(b < 0); 283 | int result = sdiv(a, b); 284 | assert(result <= 0); 285 | } 286 | 287 | function prove_divide_negative_by_positive(int a, int b) public { 288 | require(a < 0); 289 | require(b > 0); 290 | int result = sdiv(a, b); 291 | assert(result <= 0); 292 | } 293 | 294 | function prove_divide_negative_by_negative(int a, int b) public { 295 | require(a < 0); 296 | require(b < 0); 297 | int result = sdiv(a, b); 298 | assert(result >= 0); 299 | } 300 | } 301 | 302 | contract SignedModuloProperties is DSTest { 303 | // helpers 304 | function smod(int a, int b) internal pure returns (int res) { 305 | assembly { res := smod(a, b) } 306 | } 307 | function abs(int x) internal pure returns (int) { 308 | return x >= 0 ? x : -x; 309 | } 310 | 311 | 312 | // properties 313 | function prove_smod_by_zero(int a) public pure { 314 | int res = smod(a,0); 315 | assert(res == 0); 316 | } 317 | function prove_range(int a, int b) public pure { 318 | require(b != 0, "Modulo by zero is not allowed"); 319 | int result = smod(a, b); 320 | assert(abs(result) < abs(b)); 321 | } 322 | function prove_smod_non_comm(int a, int b) public pure { 323 | require(b != 0 && a != 0, "One of the values is zero"); 324 | int resultA = smod(a, b); 325 | int resultB = smod(b, a); 326 | assert(resultA != resultB || abs(a) == abs(b)); 327 | } 328 | function prove_smod_preserves_sign(int a, int b) public pure { 329 | require(b != 0, "Modulo by zero is not allowed"); 330 | int result = smod(a, b); 331 | assert((a > 0 && result >= 0) || (a < 0 && result <= 0)); 332 | } 333 | } 334 | 335 | contract SignExtendProperties is DSTest { 336 | function signExtend(uint8 byteNumber, int256 value) private pure returns (int256 res) { 337 | assembly { 338 | res := signextend(byteNumber, value) 339 | } 340 | } 341 | 342 | function prove_preservation_of_sign(int8 a) public pure { 343 | int256 extended = signExtend(0, a); // extend from 8-bit to 256-bit 344 | assert((a < 0 && extended < 0) || (a >= 0 && extended >= 0)); 345 | } 346 | 347 | function prove_preservation_of_value_for_non_negative(int8 a) public pure { 348 | require(a >= 0); 349 | int256 extended = signExtend(0, a); // extend from 8-bit to 256-bit 350 | assert(extended == a); 351 | } 352 | 353 | function prove_correct_extension_for_negative_numbers(int8 a) public pure { 354 | require(a < 0); 355 | int256 extended = signExtend(0, a); // extend from 8-bit to 256-bit 356 | int256 reExtended = signExtend(0, extended); // extend again 357 | assert(extended == reExtended); // should remain the same after re-extension 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /src/safe/ds-test/assert-true.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | import {DSTest} from "ds-test/test.sol"; 4 | 5 | contract AssertTrue is DSTest { 6 | function prove_assert_true() public pure { 7 | assert(true); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/safe/ds-test/bitwise-safe.sol: -------------------------------------------------------------------------------- 1 | import {DSTest} from "ds-test/test.sol"; 2 | 3 | contract BitwiseAndProperties is DSTest { 4 | function prove_idempotence(uint a) public pure { 5 | assert((a & a) == a); 6 | } 7 | function prove_commutativity(uint a, uint b) public pure { 8 | assert((a & b) == (b & a)); 9 | } 10 | function prove_associativity(uint a, uint b, uint c) public pure { 11 | assert((a & (b & c)) == ((a & b) & c)); 12 | } 13 | function prove_annihilation(uint a) public pure { 14 | assert((a & 0) == 0); 15 | } 16 | function prove_identity(uint a) public pure { 17 | assert((a & ~uint(0)) == a); 18 | } 19 | } 20 | 21 | contract BitwiseOrProperties is DSTest { 22 | function prove_idempotence(uint a) public pure { 23 | assert((a | a) == a); 24 | } 25 | function prove_commutativity(uint a, uint b) public pure { 26 | assert((a | b) == (b | a)); 27 | } 28 | function prove_associativity(uint a, uint b, uint c) public pure { 29 | assert((a | (b | c)) == ((a | b) | c)); 30 | } 31 | function prove_dominance(uint a) public pure { 32 | assert((a | ~uint(0)) == ~uint(0)); 33 | } 34 | function prove_identity(uint a) public pure { 35 | assert((a | 0) == a); 36 | } 37 | } 38 | 39 | contract BitwiseXorProperties is DSTest { 40 | function prove_idempotence(uint a) public pure { 41 | assert((a ^ 0) == a); 42 | } 43 | 44 | function prove_self_inversion(uint a) public pure { 45 | assert((a ^ a) == 0); 46 | } 47 | 48 | function prove_commutativity(uint a, uint b) public pure { 49 | assert((a ^ b) == (b ^ a)); 50 | } 51 | 52 | function prove_associativity(uint a, uint b, uint c) public pure { 53 | assert((a ^ (b ^ c)) == ((a ^ b) ^ c)); 54 | } 55 | 56 | function prove_inversion(uint a) public pure { 57 | assert((a ^ ~uint(0)) == ~a); 58 | } 59 | } 60 | 61 | contract BitwiseNotProperties is DSTest { 62 | function prove_double_negation(uint a) public pure { 63 | assert(~~a == a); 64 | } 65 | 66 | function prove_inversion(uint a) public pure { 67 | uint not_a = ~a; 68 | assert(a + not_a == ~uint(0)); 69 | } 70 | } 71 | 72 | contract BitwiseShlProperties is DSTest { 73 | function prove_zero_shift(uint a) public pure { 74 | assert((a << 0) == a); 75 | } 76 | 77 | function prove_distributivity_over_addition(uint a, uint b, uint c) public pure { 78 | assert(((a + b) << c) == ((a << c) + (b << c))); 79 | } 80 | 81 | function prove_multiplication_by_powers_of_two(uint a) public pure { 82 | assert((a << 1) == a * 2); 83 | } 84 | 85 | function prove_combinability_of_shifts(uint a, uint b, uint c) public pure { 86 | assert(((a << b) << c) == (a << (b + c))); 87 | } 88 | } 89 | 90 | contract BitwiseShrProperties is DSTest { 91 | function prove_zero_shift(uint a) public pure { 92 | assert((a >> 0) == a); 93 | } 94 | 95 | function prove_division_by_powers_of_two(uint a) public pure { 96 | assert((a >> 1) == a / 2); 97 | } 98 | 99 | function prove_combinability_of_shifts(uint a, uint b, uint c) public pure { 100 | assert(((a >> b) >> c) == (a >> (b + c))); 101 | } 102 | } 103 | 104 | // TODO: SAR is so weird, what should the behaviour even be? 105 | contract BitwiseSarProperties is DSTest { 106 | function sar(int num, uint shift) internal pure returns (int) { 107 | int res; 108 | assembly { 109 | res := sar(shift, num) 110 | } 111 | 112 | } 113 | // TODO: both halmos and hevm report a cex here 114 | //function prove_zero_shift(int a) public pure { 115 | //assert(sar(a, 0) == a); 116 | //} 117 | 118 | // TODO: both halmos and hevm report a cex here 119 | //function prove_division_by_powers_of_two_for_non_negative(int a, uint shift) public pure { 120 | //require(a >= 0); 121 | //assert(sar(a, 1) == a / int256(2**shift)); 122 | //} 123 | 124 | // TODO: both halmos and hevm report a cex here 125 | //function prove_effect_on_negative_numbers(int a, uint shift) public pure { 126 | //require(a < 0); 127 | //assert(sar(a,shift) == a >> shift); 128 | //} 129 | 130 | // TODO: is this true? 131 | //function prove_combinability_of_shifts(int a, uint b, uint c) public pure { 132 | //assert(((a >> b) >> c) == (a >> (b + c))); 133 | //} 134 | } 135 | -------------------------------------------------------------------------------- /src/safe/ds-test/calldata-safe.sol: -------------------------------------------------------------------------------- 1 | import {DSTest} from "ds-test/test.sol"; 2 | 3 | contract CalldataPropertiesSafe is DSTest { 4 | // read a symbolic index (less than length) 5 | function prove_read_symbolic_inbounds_safe(uint x, uint) public pure { 6 | require(x + 32 <= msg.data.length); 7 | bytes32 res; 8 | assembly { res := calldataload(x) } 9 | assert(0 <= uint(res) && uint(res) <= type(uint).max); 10 | } 11 | 12 | // read a symbolic index (past length) 13 | function prove_read_symbolic_past_length_safe(uint x, uint) public pure { 14 | require(x > msg.data.length); 15 | bytes32 res; 16 | assembly { res := calldataload(x) } 17 | assert(uint(res) == 0); 18 | } 19 | 20 | // calldata must be at least 4 bytes to hit this assert... 21 | function prove_calldata_min_size() public pure { 22 | assert(msg.data.length >= 4); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/safe/ds-test/constructors.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.19; 2 | 3 | import "ds-test/test.sol"; 4 | 5 | contract ConstructorArg { 6 | address immutable public a; 7 | constructor(address _a) { 8 | a = _a; 9 | } 10 | } 11 | 12 | contract ConstructorPropertiesSafe is DSTest { 13 | function prove_constrArgs(address b) public { 14 | ConstructorArg c = new ConstructorArg(b); 15 | assert(b == c.a()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/safe/ds-test/deposit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | import {DSTest} from "ds-test/test.sol"; 3 | 4 | // This interface is designed to be compatible with the Vyper version. 5 | /// @notice This is the Ethereum 2.0 deposit contract interface. 6 | /// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs 7 | interface IDepositContract { 8 | /// @notice A processed deposit event. 9 | event DepositEvent( 10 | bytes pubkey, 11 | bytes withdrawal_credentials, 12 | bytes amount, 13 | bytes signature, 14 | bytes index 15 | ); 16 | 17 | /// @notice Submit a Phase 0 DepositData object. 18 | /// @param pubkey A BLS12-381 public key. 19 | /// @param withdrawal_credentials Commitment to a public key for withdrawals. 20 | /// @param signature A BLS12-381 signature. 21 | /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object. 22 | /// Used as a protection against malformed input. 23 | function deposit( 24 | bytes calldata pubkey, 25 | bytes calldata withdrawal_credentials, 26 | bytes calldata signature, 27 | bytes32 deposit_data_root 28 | ) external payable; 29 | 30 | /// @notice Query the current deposit root hash. 31 | /// @return The deposit root hash. 32 | function get_deposit_root() external view returns (bytes32); 33 | 34 | /// @notice Query the current deposit count. 35 | /// @return The deposit count encoded as a little endian 64-bit number. 36 | function get_deposit_count() external view returns (bytes memory); 37 | } 38 | 39 | // Based on official specification in https://eips.ethereum.org/EIPS/eip-165 40 | interface ERC165 { 41 | /// @notice Query if a contract implements an interface 42 | /// @param interfaceId The interface identifier, as specified in ERC-165 43 | /// @dev Interface identification is specified in ERC-165. This function 44 | /// uses less than 30,000 gas. 45 | /// @return `true` if the contract implements `interfaceId` and 46 | /// `interfaceId` is not 0xffffffff, `false` otherwise 47 | function supportsInterface(bytes4 interfaceId) external pure returns (bool); 48 | } 49 | 50 | // This is a rewrite of the Vyper Eth2.0 deposit contract in Solidity. 51 | // It tries to stay as close as possible to the original source code. 52 | /// @notice This is the Ethereum 2.0 deposit contract interface. 53 | /// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs 54 | contract DepositContract is IDepositContract, ERC165 { 55 | uint constant DEPOSIT_CONTRACT_TREE_DEPTH = 32; 56 | // NOTE: this also ensures `deposit_count` will fit into 64-bits 57 | uint constant MAX_DEPOSIT_COUNT = 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1; 58 | 59 | bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch; 60 | uint256 deposit_count; 61 | 62 | bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes; 63 | 64 | constructor() { 65 | // Compute hashes in empty sparse Merkle tree 66 | for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++) 67 | zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height])); 68 | } 69 | 70 | function get_deposit_root() override external view returns (bytes32) { 71 | bytes32 node; 72 | uint size = deposit_count; 73 | for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) { 74 | if ((size & 1) == 1) 75 | node = sha256(abi.encodePacked(branch[height], node)); 76 | else 77 | node = sha256(abi.encodePacked(node, zero_hashes[height])); 78 | size /= 2; 79 | } 80 | return sha256(abi.encodePacked( 81 | node, 82 | to_little_endian_64(uint64(deposit_count)), 83 | bytes24(0) 84 | )); 85 | } 86 | 87 | function get_deposit_count() override external view returns (bytes memory) { 88 | return to_little_endian_64(uint64(deposit_count)); 89 | } 90 | 91 | function deposit( 92 | bytes calldata pubkey, 93 | bytes calldata withdrawal_credentials, 94 | bytes calldata signature, 95 | bytes32 deposit_data_root 96 | ) override external payable { 97 | // Extended ABI length checks since dynamic types are used. 98 | require(pubkey.length == 48, "DepositContract: invalid pubkey length"); 99 | require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length"); 100 | require(signature.length == 96, "DepositContract: invalid signature length"); 101 | 102 | // Check deposit amount 103 | require(msg.value >= 1 ether, "DepositContract: deposit value too low"); 104 | require(msg.value % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei"); 105 | uint deposit_amount = msg.value / 1 gwei; 106 | require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high"); 107 | 108 | // Emit `DepositEvent` log 109 | bytes memory amount = to_little_endian_64(uint64(deposit_amount)); 110 | emit DepositEvent( 111 | pubkey, 112 | withdrawal_credentials, 113 | amount, 114 | signature, 115 | to_little_endian_64(uint64(deposit_count)) 116 | ); 117 | 118 | // Compute deposit data root (`DepositData` hash tree root) 119 | bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, bytes16(0))); 120 | bytes32 signature_root = sha256(abi.encodePacked( 121 | sha256(abi.encodePacked(signature[:64])), 122 | sha256(abi.encodePacked(signature[64:], bytes32(0))) 123 | )); 124 | bytes32 node = sha256(abi.encodePacked( 125 | sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)), 126 | sha256(abi.encodePacked(amount, bytes24(0), signature_root)) 127 | )); 128 | 129 | // Verify computed and expected deposit data roots match 130 | require(node == deposit_data_root, "DepositContract: reconstructed DepositData does not match supplied deposit_data_root"); 131 | 132 | // Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`) 133 | require(deposit_count < MAX_DEPOSIT_COUNT, "DepositContract: merkle tree full"); 134 | 135 | // Add deposit data root to Merkle tree (update a single `branch` node) 136 | deposit_count += 1; 137 | uint size = deposit_count; 138 | for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) { 139 | if ((size & 1) == 1) { 140 | branch[height] = node; 141 | return; 142 | } 143 | node = sha256(abi.encodePacked(branch[height], node)); 144 | size /= 2; 145 | } 146 | // As the loop should always end prematurely with the `return` statement, 147 | // this code should be unreachable. We assert `false` just to be safe. 148 | assert(false); 149 | } 150 | 151 | function supportsInterface(bytes4 interfaceId) override external pure returns (bool) { 152 | return interfaceId == type(ERC165).interfaceId || interfaceId == type(IDepositContract).interfaceId; 153 | } 154 | 155 | function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) { 156 | ret = new bytes(8); 157 | bytes8 bytesValue = bytes8(value); 158 | // Byteswapping during copying to bytes. 159 | ret[0] = bytesValue[7]; 160 | ret[1] = bytesValue[6]; 161 | ret[2] = bytesValue[5]; 162 | ret[3] = bytesValue[4]; 163 | ret[4] = bytesValue[3]; 164 | ret[5] = bytesValue[2]; 165 | ret[6] = bytesValue[1]; 166 | ret[7] = bytesValue[0]; 167 | } 168 | } 169 | 170 | 171 | contract DepositTest is DSTest { 172 | DepositContract deposit = new DepositContract(); 173 | 174 | // we used fixed size types to make life easier and since this is what the 175 | // deposit contract actually expects 176 | function prove_deposit( 177 | bytes32 pubkey0, 178 | bytes16 pubkey1, 179 | bytes32 withdrawal_credentials, 180 | bytes32 sig0, 181 | bytes32 sig1, 182 | bytes32 sig2, 183 | bytes32 deposit_data_root) 184 | public { 185 | // assertion in deposit should be unreachable... 186 | deposit.deposit{ value : 1 ether }( 187 | abi.encodePacked(pubkey0, pubkey1), 188 | abi.encodePacked(withdrawal_credentials), 189 | abi.encodePacked(sig0, sig1, sig2), 190 | deposit_data_root 191 | ); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/safe/ds-test/erc20.sol: -------------------------------------------------------------------------------- 1 | import "ds-test/test.sol"; 2 | import "src/common/erc20.sol"; 3 | 4 | contract SolidityTestPass is DSTest { 5 | ERC20 token; 6 | 7 | function setUp() public { 8 | token = new ERC20("tkn", "tkn", 18); 9 | } 10 | 11 | function prove_balance(address usr, uint amt) public { 12 | assert(0 == token.balanceOf(usr)); 13 | token.mint(usr, amt); 14 | assert(amt == token.balanceOf(usr)); 15 | } 16 | 17 | function prove_supply(uint supply) public { 18 | token.mint(address(this), supply); 19 | uint actual = token.totalSupply(); 20 | assert(supply == actual); 21 | } 22 | 23 | function prove_burn(uint supply, uint amt) public { 24 | if (amt > supply) return; // no undeflow 25 | 26 | token.mint(address(this), supply); 27 | token.burn(address(this), amt); 28 | 29 | assert(supply - amt == token.totalSupply()); 30 | } 31 | 32 | function prove_transfer(uint supply, address usr, uint amt) public { 33 | token.mint(address(this), supply); 34 | 35 | uint prebal = token.balanceOf(usr); 36 | token.transfer(usr, amt); 37 | uint postbal = token.balanceOf(usr); 38 | 39 | uint expected = usr == address(this) 40 | ? 0 // self transfer is a noop 41 | : amt; // otherwise `amt` has been transfered to `usr` 42 | assert(expected == postbal - prebal); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/safe/ds-test/keccak.sol: -------------------------------------------------------------------------------- 1 | import {DSTest} from "ds-test/test.sol"; 2 | 3 | contract KeccakProperties is DSTest { 4 | 5 | // --- injectivity --- 6 | 7 | function prove_injectivity_uint256_diff(uint x, uint y) public { 8 | require(x != y); 9 | assert(keccak256(abi.encodePacked(x)) != keccak256(abi.encodePacked(y))); 10 | } 11 | function prove_injectivity_uint256_same(uint x) public { 12 | bytes32 k0 = keccak256(abi.encodePacked(x)); 13 | bytes32 k1 = keccak256(abi.encodePacked(x)); 14 | assert(k0 == k1); 15 | } 16 | function prove_injectivity_uint128_diff(uint128 x, uint128 y) public { 17 | require(x != y); 18 | assert(keccak256(abi.encodePacked(x)) != keccak256(abi.encodePacked(y))); 19 | } 20 | function prove_injectivity_uint128_same(uint128 x) public { 21 | bytes32 k0 = keccak256(abi.encodePacked(x)); 22 | bytes32 k1 = keccak256(abi.encodePacked(x)); 23 | assert(k0 == k1); 24 | } 25 | function prove_injectivity_mixed_sign(int128 x, uint256 y) public { 26 | assert(keccak256(abi.encodePacked(x)) != keccak256(abi.encodePacked(y))); 27 | } 28 | function prove_injectivity_mixed_width1(uint128 x, uint256 y) public { 29 | assert(keccak256(abi.encodePacked(x)) != keccak256(abi.encodePacked(y))); 30 | } 31 | function prove_injectivity_mixed_width2(int128 x, int32 y) public { 32 | assert(keccak256(abi.encodePacked(x)) != keccak256(abi.encodePacked(y))); 33 | } 34 | function prove_injectivity_bytes32_diff(bytes32 x, bytes32 y) public { 35 | require(x != y); 36 | assert(keccak256(abi.encodePacked(x)) != keccak256(abi.encodePacked(y))); 37 | } 38 | function prove_injectivity_bytes32_same(uint128 x) public { 39 | bytes32 k0 = keccak256(abi.encodePacked(x)); 40 | bytes32 k1 = keccak256(abi.encodePacked(x)); 41 | assert(k0 == k1); 42 | } 43 | function prove_injectivity_mixed_bytes(bytes32 x, bytes16 y) public { 44 | assert(keccak256(abi.encodePacked(x)) != keccak256(abi.encodePacked(y))); 45 | } 46 | function prove_injectivity_array_calldata(uint[4] calldata x, uint[4] calldata y) public { 47 | require(x[0] != y[0] || x[1] != y[1] || x[2] != y[2] || x[3] != y[3]); 48 | assert(keccak256(abi.encodePacked(x)) != keccak256(abi.encodePacked(y))); 49 | } 50 | function prove_injectivity_array_mixed_calldata(uint[4] calldata x, uint[3] calldata y) public { 51 | assert(keccak256(abi.encodePacked(x)) != keccak256(abi.encodePacked(y))); 52 | } 53 | function prove_injectivity_array_memory(uint[4] memory x, uint[4] memory y) public { 54 | require(x[0] != y[0] || x[1] != y[1] || x[2] != y[2] || x[3] != y[3]); 55 | assert(keccak256(abi.encodePacked(x)) != keccak256(abi.encodePacked(y))); 56 | } 57 | function prove_injectivity_array_mixed_memory(uint[4] memory x, uint[3] memory y) public { 58 | assert(keccak256(abi.encodePacked(x)) != keccak256(abi.encodePacked(y))); 59 | } 60 | function prove_injectivity_dynamic_array_same(bytes memory x) public { 61 | bytes32 k1 = keccak256(x); 62 | bytes32 k2 = keccak256(x); 63 | assert(k1 == k2); 64 | } 65 | 66 | // --- large gaps --- 67 | 68 | function prove_large_gaps_uint256(uint x) public { 69 | uint k0 = uint(keccak256(abi.encodePacked(x))); 70 | uint k1 = uint(keccak256(abi.encodePacked(x + 1))); 71 | uint diff = k1 > k0 ? k1 - k0 : k0 - k1; 72 | unchecked { assert(k0 - k1 > 10000); } 73 | } 74 | function prove_large_gaps_int256(int x) public { 75 | uint k0 = uint(keccak256(abi.encodePacked(x))); 76 | uint k1 = uint(keccak256(abi.encodePacked(x - 1))); 77 | unchecked { assert(k0 - k1 > 10000); } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/safe/ds-test/loops-safe.sol: -------------------------------------------------------------------------------- 1 | import {DSTest} from "ds-test/test.sol"; 2 | 3 | contract LoopsSafe is DSTest { 4 | function prove_bounded_smol(uint x) public { 5 | require (x < 5); 6 | uint j; 7 | for (uint i = 0; i < x; i++) { 8 | j ++; 9 | } 10 | assert(j == x); 11 | } 12 | 13 | function prove_bounded_med(uint x) public { 14 | require (x < 100); 15 | uint j; 16 | for (uint i = 0; i < x; i++) { 17 | j ++; 18 | } 19 | assert(j == x); 20 | } 21 | 22 | function prove_bounded_large(uint x) public { 23 | require (x < 10_000); 24 | uint j; 25 | for (uint i = 0; i < x; i++) { 26 | j ++; 27 | } 28 | assert(j == x); 29 | } 30 | 31 | function prove_unbounded(uint x) public { 32 | uint j; 33 | for (uint i = 0; i < x; i++) { 34 | j ++; 35 | } 36 | assert(j == x); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/safe/ds-test/memory-safe.sol: -------------------------------------------------------------------------------- 1 | import {DSTest} from "ds-test/test.sol"; 2 | 3 | contract MemoryPropertiesSafe is DSTest { 4 | function prove_load_symbolic_index(uint idx) public { 5 | uint res; 6 | assembly { res := mload(idx) } 7 | assert(0 <= res && res <= type(uint).max); 8 | } 9 | 10 | function prove_store_read_same_index_sym(uint idx, uint val) public { 11 | uint res; 12 | assembly { 13 | mstore(idx, val) 14 | res := mload(idx) 15 | } 16 | assert(res == val); 17 | } 18 | 19 | function prove_store_read_same_index_conc(uint val) public { 20 | uint res; 21 | assembly { 22 | mstore(1000, val) 23 | res := mload(1000) 24 | } 25 | assert(res == val); 26 | } 27 | 28 | function prove_read_non_aligned_conc_idx(uint val) public { 29 | uint res; 30 | assembly { 31 | mstore(0, val) 32 | mstore(32, 0) 33 | res := mload(16) 34 | } 35 | assert(res == (val << 128)); 36 | } 37 | 38 | function prove_read_non_aligned_sym_idx(uint idx, uint val) public { 39 | uint res; 40 | uint next_word = idx + 32; 41 | uint half_word = idx + 16; 42 | assembly { 43 | mstore(idx, val) 44 | mstore(next_word, 0) 45 | res := mload(half_word) 46 | } 47 | assert(res == (val << 128)); 48 | } 49 | 50 | function prove_read_stacked_write_conc_idx(uint x, uint y) public { 51 | uint res; 52 | assembly { 53 | mstore(0, x) 54 | mstore(0, y) 55 | res := mload(0) 56 | } 57 | assert(res == y); 58 | } 59 | 60 | function prove_read_stacked_write_sym_idx(uint idx, uint x, uint y) public { 61 | require(idx < type(uint64).max); 62 | uint res; 63 | assembly { 64 | mstore(idx, x) 65 | mstore(idx, y) 66 | res := mload(0) 67 | } 68 | assert(res == y); 69 | } 70 | 71 | function prove_read_stacked_write_non_aligned_conc_idx(uint x, uint y) public { 72 | uint res; 73 | assembly { 74 | mstore(0, x) 75 | mstore(16, y) 76 | res := mload(0) 77 | } 78 | uint x_bits = (x >> 128) << 128; 79 | uint y_bits = (y >> 128); 80 | assert(res == x_bits | y_bits); 81 | } 82 | 83 | function prove_read_stacked_write_non_aligned_sym_idx(uint idx, uint x, uint y) public { 84 | require(idx < type(uint16).max); 85 | uint res; 86 | uint half_word = idx + 16; 87 | assembly { 88 | mstore(idx, x) 89 | mstore(half_word, y) 90 | res := mload(idx) 91 | } 92 | uint x_bits = (x >> 128) << 128; 93 | uint y_bits = (y >> 128); 94 | assert(res == x_bits | y_bits); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/safe/ds-test/storage-safe.sol: -------------------------------------------------------------------------------- 1 | import {DSTest} from "ds-test/test.sol"; 2 | 3 | contract ValueTypesSafe is DSTest { 4 | uint x; 5 | uint y; 6 | 7 | function prove_initial_values() public { 8 | assert(x == 0); 9 | assert(y == 0); 10 | } 11 | 12 | function prove_simple_write(uint a) public { 13 | x = a; 14 | y = a; 15 | assert(x == a); 16 | assert(y == a); 17 | } 18 | 19 | function prove_write_twice(uint a, uint b) public { 20 | x = a; 21 | x = b; 22 | assert(x == b); 23 | } 24 | 25 | function prove_stress_value(uint v) public { 26 | x = v; 27 | y = x; 28 | x = y + 1; 29 | y = x -1; 30 | x = y * 2; 31 | y = x / 2; 32 | assert(y == v); 33 | } 34 | } 35 | 36 | contract MappingPropertiesSafe is DSTest { 37 | mapping (address => uint) balances; 38 | mapping (uint => bool) auth; 39 | mapping (address => mapping (address => uint)) allowance; 40 | 41 | function prove_mapping_access0(address x, address y) public { 42 | require(x != y); 43 | balances[x] = 1; 44 | balances[y] = 2; 45 | assert(balances[x] != balances[y]); 46 | } 47 | 48 | function prove_nested_set(address x, address y, uint val) public { 49 | allowance[x][y] = val; 50 | assert(allowance[x][y] == val); 51 | } 52 | 53 | function prove_initial_values(address x, address y) public { 54 | assert(balances[x] == 0); 55 | assert(allowance[x][y] == 0); 56 | assert(auth[uint256(uint160(x))] == false); 57 | } 58 | 59 | function prove_mixed_symoblic_concrete_writes(address x, uint v) public { 60 | balances[x] = v; 61 | balances[address(0)] = balances[x]; 62 | assert(balances[address(0)] == v); 63 | } 64 | 65 | function prove_stress_mapping(address x, address y, uint val) public { 66 | balances[x] = val; 67 | allowance[x][y] = balances[x]; 68 | allowance[y][x] = allowance[x][y]; 69 | auth[uint256(uint160(x))] = true; 70 | if (auth[uint256(uint160(x))]) { 71 | balances[y] = allowance[y][x]; 72 | assert(balances[y] == val); 73 | } else { 74 | assert(false); 75 | } 76 | } 77 | } 78 | 79 | contract StructPropertiesSafe is DSTest { 80 | struct S { 81 | uint x; 82 | uint y; 83 | uint z; 84 | } 85 | 86 | S s; 87 | mapping(uint => S) map; 88 | 89 | function prove_read_write(uint a, uint b, uint c) public { 90 | s.x = a; 91 | s.y = b; 92 | s.z = c; 93 | assert(s.x == a); 94 | assert(s.y == b); 95 | assert(s.z == c); 96 | } 97 | 98 | function prove_mapping_access1(uint idx, uint val) public { 99 | map[idx].x = val; 100 | map[idx + 1].y = map[idx].x; 101 | map[idx - 1].z = map[idx + 1].y; 102 | assert(map[idx -1].z == val); 103 | } 104 | } 105 | 106 | contract ArrayPropertiesSafe is DSTest { 107 | uint[] arr1; 108 | uint[][] arr2; 109 | 110 | function prove_append_one(uint v) public { 111 | arr1.push(v); 112 | assert(arr1[0] == v); 113 | assert(arr1.length == 1); 114 | } 115 | 116 | function test_cex() public { 117 | prove_nested_append(0,2); 118 | } 119 | 120 | function prove_nested_append(uint v, uint w) public { 121 | arr2.push([v,w]); 122 | arr2.push(); 123 | arr2.push(); 124 | 125 | arr2[1].push(arr2[0][0]); 126 | 127 | arr2[2].push(w); 128 | arr2[1].push(1); 129 | 130 | assert(arr2.length == 3); 131 | 132 | assert(arr2[0].length == 2); 133 | assert(arr2[0][0] == v); 134 | assert(arr2[0][1] == w); 135 | 136 | assert(arr2[1].length == 2); 137 | assert(arr2[1][0] == v); 138 | assert(arr2[1][1] == 1); 139 | 140 | assert(arr2[2].length == 1); 141 | assert(arr2[2][0] == w); 142 | } 143 | } 144 | 145 | contract PackedStoragePropertiesSafe is DSTest { 146 | uint128 a; 147 | uint64 b; 148 | bytes4 c; 149 | bool d; 150 | bool e; 151 | bool f; 152 | 153 | function prove_packed_storage_access(uint128 x, uint64 y) public { 154 | a = x; 155 | b = uint64(y); 156 | c = bytes4(bytes32(uint256(x))); 157 | d = uint128(uint256(bytes32(c))) > x; 158 | e = !d; 159 | f = e || d; 160 | assert(f); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/unsafe/1tx-abstract/sym-storage-unsafe.sol: -------------------------------------------------------------------------------- 1 | contract SymStorageUnsafe { 2 | uint x; 3 | mapping (uint => uint) map; 4 | uint[] arr; 5 | 6 | function prove_value(uint v) public { 7 | assert(x == 10); 8 | } 9 | 10 | function prove_mapping(uint u, uint v) public { 11 | assert(map[u] == map[v]); 12 | } 13 | 14 | function prove_array(uint i, uint j) public { 15 | assert(arr[i] == arr[j]); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/unsafe/ds-test/arith-unsafe.sol: -------------------------------------------------------------------------------- 1 | import {DSTest} from "ds-test/test.sol"; 2 | 3 | contract ArithmeticPropertiesUncheckedUnsafe is DSTest { 4 | function prove_add_mul_distributivity(uint x, uint y, uint z) public pure { 5 | unchecked { 6 | assert(x * (y + y) == (x * y) + (x * z)); 7 | } 8 | } 9 | function prove_sub_comm(uint x, uint y) public pure { 10 | unchecked { 11 | assert(x - y == y - x); 12 | } 13 | } 14 | function prove_sub_assoc(uint x, uint y, uint z) public pure { 15 | unchecked { 16 | assert(x - (y - z) == (x - y) - z); 17 | } 18 | } 19 | function prove_div_distr(uint x, uint y, uint z) public pure { 20 | unchecked { 21 | assert(x / (y + z) == (x / y) + (x / z)); 22 | } 23 | } 24 | function prove_div_assoc(uint x, uint y, uint z) public pure { 25 | unchecked { 26 | assert(x / (y / z) == (x / y) / z); 27 | } 28 | } 29 | function prove_sub_larger(int x, int y) public pure { 30 | require(x > y); 31 | unchecked { 32 | assert(y - x < 0); 33 | } 34 | } 35 | 36 | function prove_add2(uint x, uint y) public pure { 37 | unchecked { 38 | assert(x + y >= x); 39 | } 40 | } 41 | } 42 | 43 | contract ArithmeticPropertiesCheckedUnsafe is DSTest { 44 | function prove_sub_assoc(uint x, uint y, uint z) public pure { 45 | assert(x - (y - z) == (x - y) - z); 46 | } 47 | function prove_div_distr(uint x, uint y, uint z) public pure { 48 | assert(x / (y + z) == (x / y) + (x / z)); 49 | } 50 | function prove_div_assoc(uint x, uint y, uint z) public pure { 51 | assert(x / (y / z) == (x / y) / z); 52 | } 53 | function prove_sdiv_comm(int a, int b) public pure { 54 | require(a != 0 && b != 0, "One of the values is zero"); 55 | assert(a / b == b / a); 56 | } 57 | function prove_distributivity(uint120 x, uint120 y, uint120 z) public pure { 58 | assert(x + (y * z) == (x + y) * (x + z)); 59 | } 60 | function prove_complicated(uint x, uint y, uint z) public pure { 61 | assert((((x * y) / z) * x) / (x * y * z) == (((x * y) + z) / x) * (y / z)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/unsafe/ds-test/assert-false.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | import {DSTest} from "ds-test/test.sol"; 4 | 5 | contract AssertFalse is DSTest { 6 | function prove_assert_false() public pure { 7 | assert(false); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/unsafe/ds-test/bad-vault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // contributed by @karmacoma on 5 Aug 2023 3 | 4 | pragma solidity ^0.8.15; 5 | 6 | import "forge-std/Test.sol"; 7 | import {console2} from "forge-std/console2.sol"; 8 | 9 | // inspired by the classic reentrancy level in Ethernaut CTF 10 | contract BadVault { 11 | mapping(address => uint256) public balance; 12 | 13 | function deposit() external payable { 14 | balance[msg.sender] += msg.value; 15 | 16 | console2.log("deposit", msg.sender, msg.value); 17 | } 18 | 19 | function withdraw(uint256 amount) external { 20 | // checks 21 | uint256 _balance = balance[msg.sender]; 22 | require(_balance >= amount, "insufficient balance"); 23 | 24 | console2.log("withdraw", msg.sender, amount); 25 | 26 | // interactions 27 | (bool success,) = msg.sender.call{value: amount}(""); 28 | require(success, "transfer failed"); 29 | 30 | // effects 31 | balance[msg.sender] = _balance - amount; 32 | } 33 | } 34 | 35 | // from https://github.com/mds1/multicall 36 | struct Call3Value { 37 | address target; 38 | uint256 value; 39 | bytes data; 40 | } 41 | 42 | contract ExploitLaunchPad { 43 | address public owner; 44 | bool reentered; 45 | 46 | Call3Value public call; 47 | 48 | constructor() { 49 | owner = msg.sender; 50 | } 51 | 52 | receive() external payable { 53 | if (reentered) { 54 | return; 55 | } 56 | 57 | require(call.value <= address(this).balance, "insufficient balance"); 58 | 59 | reentered = true; 60 | (bool success,) = call.target.call{value: call.value}(call.data); 61 | reentered = false; 62 | } 63 | 64 | function defer(Call3Value calldata _call) external payable { 65 | require(msg.sender == owner, "only owner"); 66 | call = _call; 67 | } 68 | 69 | function go(Call3Value calldata _call) 70 | external 71 | payable 72 | { 73 | require(msg.sender == owner, "only owner"); 74 | require(_call.value <= address(this).balance, "insufficient balance"); 75 | 76 | (bool success,) = _call.target.call{value: _call.value}(_call.data); 77 | } 78 | 79 | function deposit() external payable {} 80 | 81 | function withdraw() external { 82 | owner.call{value: address(this).balance}(""); 83 | } 84 | } 85 | 86 | contract BadVaultTest is Test { 87 | BadVault vault; 88 | ExploitLaunchPad exploit; 89 | 90 | address user1; 91 | address user2; 92 | address attacker; 93 | 94 | function setUp() public { 95 | vault = new BadVault(); 96 | 97 | user1 = address(1); 98 | user2 = address(2); 99 | 100 | vm.deal(user1, 1 ether); 101 | vm.prank(user1); 102 | vault.deposit{value: 1 ether}(); 103 | 104 | vm.deal(user2, 1 ether); 105 | vm.prank(user2); 106 | vault.deposit{value: 1 ether}(); 107 | 108 | attacker = address(42); 109 | vm.prank(attacker); 110 | exploit = new ExploitLaunchPad(); 111 | 112 | assert(exploit.owner() == attacker); 113 | } 114 | 115 | /// @custom:halmos --array-lengths data1=36,data2=36,deferredData=36 116 | function prove_BadVault_usingExploitLaunchPad( 117 | address target1, 118 | uint256 amount1, 119 | bytes memory data1, 120 | 121 | address target2, 122 | uint256 amount2, 123 | bytes memory data2, 124 | 125 | address deferredTarget, 126 | uint256 deferredAmount, 127 | bytes memory deferredData 128 | 129 | ) public { 130 | uint256 STARTING_BALANCE = 2 ether; 131 | vm.deal(attacker, STARTING_BALANCE); 132 | 133 | vm.assume(address(exploit).balance == 0); 134 | vm.assume((amount1 + amount2) <= STARTING_BALANCE); 135 | 136 | console2.log("attacker starting balance", address(attacker).balance); 137 | vm.prank(attacker); 138 | exploit.deposit{value: STARTING_BALANCE}(); 139 | 140 | vm.prank(attacker); 141 | exploit.go(Call3Value({ 142 | target: target1, 143 | value: amount1, 144 | data: data1 145 | })); 146 | 147 | vm.prank(attacker); 148 | exploit.defer(Call3Value({ 149 | target: deferredTarget, 150 | value: deferredAmount, 151 | data: deferredData 152 | })); 153 | 154 | vm.prank(attacker); 155 | exploit.go(Call3Value({ 156 | target: target2, 157 | value: amount2, 158 | data: data2 159 | })); 160 | 161 | vm.prank(attacker); 162 | exploit.withdraw(); 163 | 164 | // they can not end up with more ether than they started with 165 | console2.log("attacker final balance", address(attacker).balance); 166 | assert(attacker.balance <= STARTING_BALANCE); 167 | } 168 | 169 | // running `forge test --match-test test_BadVault_solution -vvv` confirms the attack trace: took 6s Node system at 18:00:43 170 | // deposit 0x0000000000000000000000000000000000000001 1000000000000000000 171 | // deposit 0x0000000000000000000000000000000000000002 1000000000000000000 172 | // attacker starting balance 2000000000000000000 173 | // deposit 0x5f4E4CcFF0A2553b2BDE30e1fC8531B287db9087 1000000000000000000 174 | // withdraw 0x5f4E4CcFF0A2553b2BDE30e1fC8531B287db9087 1000000000000000000 175 | // withdraw 0x5f4E4CcFF0A2553b2BDE30e1fC8531B287db9087 1000000000000000000 176 | // attacker final balance 3000000000000000000 177 | function test_BadVault_solution() public { 178 | prove_BadVault_usingExploitLaunchPad( 179 | // 1st call 180 | address(vault), 181 | 1 ether, 182 | abi.encodeWithSelector( 183 | vault.deposit.selector 184 | ), 185 | 186 | // 2nd call 187 | address(vault), 188 | 0 ether, 189 | abi.encodeWithSelector( 190 | vault.withdraw.selector, 191 | 1 ether 192 | ), 193 | 194 | // deferred call 195 | address(vault), 196 | 0 ether, 197 | abi.encodeWithSelector( 198 | vault.withdraw.selector, 199 | 1 ether 200 | ) 201 | ); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/unsafe/ds-test/branch-magic.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {DSTest} from "ds-test/test.sol"; 5 | 6 | contract PostExample { 7 | bool live; 8 | 9 | modifier isLive() { 10 | require(live); 11 | _; 12 | } 13 | 14 | function setLive(bool _live) external { 15 | live = _live; 16 | } 17 | 18 | // https://github.com/foundry-rs/foundry/issues/2851 19 | function backdoor(uint256 x) external view isLive { 20 | uint256 number = 99; 21 | unchecked { 22 | uint256 z = x - 1; 23 | if (z == 6912213124124531) { 24 | number = 0; 25 | } else { 26 | number = 1; 27 | } 28 | } 29 | assert(number != 0); 30 | } 31 | } 32 | 33 | contract PostExampleTwoLiveTest is DSTest { 34 | PostExample public example; 35 | 36 | function setUp() public { 37 | example = new PostExample(); 38 | } 39 | 40 | function proveBackdoor(bool isLive, uint256 x) public { 41 | example.setLive(isLive); 42 | example.backdoor(x); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/unsafe/ds-test/calldata-unsafe.sol: -------------------------------------------------------------------------------- 1 | import {DSTest} from "ds-test/test.sol"; 2 | 3 | contract CalldataPropertiesUnsafe is DSTest { 4 | // read a symbolic index (less than length) 5 | function prove_read_symbolic_inbounds_unsafe(uint x, uint) public pure { 6 | require(x + 32 <= msg.data.length); 7 | bytes32 res; 8 | assembly { res := calldataload(x) } 9 | assert(uint(res) == x); 10 | } 11 | 12 | // read a symbolic index (past length) 13 | function prove_read_symbolic_past_length_unsafe(uint x, uint) public pure { 14 | require(x > msg.data.length); 15 | bytes32 res; 16 | assembly { res := calldataload(x) } 17 | assert(uint(res) != 0); 18 | } 19 | 20 | // calldata can be any length 21 | function prove_calldata_abstract_size() public pure { 22 | assert(msg.data.length == 4); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/unsafe/ds-test/loops-unsafe.sol: -------------------------------------------------------------------------------- 1 | import {DSTest} from "ds-test/test.sol"; 2 | 3 | contract LoopsUnsafe is DSTest { 4 | function prove_bounded_smol(uint x) public { 5 | require (x < 5); 6 | uint j; 7 | for (uint i = 0; i < x; i++) { 8 | j ++; 9 | } 10 | assert(j != x); 11 | } 12 | 13 | function prove_bounded_med(uint x) public { 14 | require (x < 100); 15 | uint j; 16 | for (uint i = 0; i < x; i++) { 17 | j ++; 18 | } 19 | assert(j != x); 20 | } 21 | 22 | function prove_bounded_large(uint x) public { 23 | require (x < 10_000); 24 | uint j; 25 | for (uint i = 0; i < x; i++) { 26 | j ++; 27 | } 28 | assert(j != x); 29 | } 30 | 31 | function prove_unbounded(uint x) public { 32 | uint j; 33 | for (uint i = 0; i < x; i++) { 34 | j ++; 35 | } 36 | assert(j != x); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/unsafe/ds-test/memory-unsafe.sol: -------------------------------------------------------------------------------- 1 | import {DSTest} from "ds-test/test.sol"; 2 | 3 | contract MemoryPropertiesUnsafe is DSTest { 4 | function prove_load_symbolic_index(uint idx) public { 5 | uint res; 6 | assembly { res := mload(idx) } 7 | assert(res == 0); 8 | } 9 | 10 | function prove_store_read_same_index_sym(uint idx, uint val) public { 11 | uint res; 12 | assembly { 13 | mstore(idx, val) 14 | res := mload(idx) 15 | } 16 | assert(res != val); 17 | } 18 | 19 | function prove_store_read_same_index_conc(uint val) public { 20 | uint res; 21 | assembly { 22 | mstore(1000, val) 23 | res := mload(1000) 24 | } 25 | assert(res != val); 26 | } 27 | 28 | function prove_read_non_aligned_conc_idx(uint val) public { 29 | uint res; 30 | assembly { 31 | mstore(0, val) 32 | mstore(32, 0) 33 | res := mload(16) 34 | } 35 | assert(res != (val << 128)); 36 | } 37 | 38 | function prove_read_non_aligned_sym_idx(uint idx, uint val) public { 39 | uint res; 40 | uint next_word = idx + 32; 41 | uint half_word = idx + 16; 42 | assembly { 43 | mstore(idx, val) 44 | mstore(next_word, 0) 45 | res := mload(half_word) 46 | } 47 | assert(res != (val << 128)); 48 | } 49 | 50 | function prove_read_stacked_write_conc_idx(uint x, uint y) public { 51 | uint res; 52 | assembly { 53 | mstore(0, x) 54 | mstore(0, y) 55 | res := mload(0) 56 | } 57 | assert(res != y); 58 | } 59 | 60 | function prove_read_stacked_write_sym_idx(uint idx, uint x, uint y) public { 61 | uint res; 62 | assembly { 63 | mstore(idx, x) 64 | mstore(idx, y) 65 | res := mload(0) 66 | } 67 | assert(res != y); 68 | } 69 | 70 | function prove_read_stacked_write_non_aligned_conc_idx(uint x, uint y) public { 71 | uint res; 72 | assembly { 73 | mstore(0, x) 74 | mstore(16, y) 75 | res := mload(0) 76 | } 77 | uint x_bits = (x >> 128) << 128; 78 | uint y_bits = (y >> 128); 79 | assert(res != x_bits | y_bits); 80 | } 81 | 82 | function prove_read_stacked_write_non_aligned_sym_idx(uint idx, uint x, uint y) public { 83 | uint res; 84 | uint half_word = idx + 16; 85 | assembly { 86 | mstore(idx, x) 87 | mstore(half_word, y) 88 | res := mload(0) 89 | } 90 | uint x_bits = (x >> 128) << 128; 91 | uint y_bits = (y >> 128); 92 | assert(res != x_bits | y_bits); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/unsafe/ds-test/minivat.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity ^0.8.17; 3 | 4 | import "ds-test/test.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 getValues() external view returns (uint256, uint256, uint256) { 19 | return (Art, rate, debt); 20 | } 21 | 22 | function frob(int256 dart) public { 23 | address usr = msg.sender; 24 | 25 | int256 _art = int256(art[usr]) + dart; 26 | require(_art >= 0, "negative art"); 27 | art[usr] = uint256(_art); 28 | 29 | require(rate <= uint256(type(int256).max), "rate exceeds max int256"); 30 | int256 ddai = dart * int256(rate); 31 | 32 | int256 _dai = int256(dai[usr]) + ddai; 33 | require(_dai >= 0, "negative dai"); 34 | dai[usr] = uint256(_dai); 35 | 36 | Art = dart > 0 ? Art + uint256(dart) : Art - uint256(-1 * dart); 37 | debt = ddai > 0 ? debt + uint256(ddai) : debt - uint256(-1 * ddai); 38 | } 39 | 40 | function fold(int256 delta) public { 41 | address usr = msg.sender; 42 | rate = delta > 0 ? rate + uint256(delta) : rate - uint256(-1 * delta); 43 | require(Art <= uint256(type(int256).max), "Art exceeds max int256"); 44 | int256 ddai = int256(Art) * delta; 45 | dai[usr] = ddai > 0 46 | ? dai[usr] + uint256(ddai) 47 | : dai[usr] - uint256(-1 * ddai); 48 | debt = ddai > 0 ? debt + uint256(ddai) : debt - uint256(-1 * ddai); 49 | } 50 | } 51 | 52 | contract MiniVatTest is DSTest { 53 | MiniVat public vat; 54 | 55 | constructor() { 56 | vat = new MiniVat(); 57 | vat.init(); 58 | } 59 | 60 | function prove_invariant_symb(int256 frob, int256 fold) public { 61 | vat.frob(frob); 62 | vat.fold(fold); 63 | vat.init(); 64 | 65 | (uint Art, uint rate, uint debt) = vat.getValues(); 66 | assert(debt == Art * rate); 67 | } 68 | 69 | function prove_invariant_fixed() public { 70 | prove_invariant_symb(10 ** 18, -10 ** 27); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/unsafe/ds-test/sacred-geometry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | /// @notice Sacred Geometry puzzle by jtriley.eth, reproduced with permission of the author 7 | /// featured on https://www.curta.wtf/puzzle/14 8 | /// write-up: https://twitter.com/jtriley_eth/status/1683203592344473601 9 | /// quick summary: 10 | /// - implements a stack machine with add, sub, mul, div, and swap1-swap7 11 | /// - an 8-byte input is provided to the stack machine 12 | /// - the solution is interpreted as bytecode and executed by the stack machine 13 | /// - to pass the challenge, the solution must produce one of the three primes 14 | 15 | /* < Note: The program counter (`memory[0x0180]`) is implicitly initialized to 0. 272 | /// 273 | /// ### Directives 274 | /// 275 | /// 1. Store the opcode jumptable in memory. 276 | /// 2. Write the executable to memory. 277 | /// 3. Write the virtual stack depth to memory. 278 | /// 4. Read the program input from calldata. 279 | /// 5. Get and push each random number to the stack with `PUSH_NUMBER`. 280 | #define macro INITIALIZE_VM() = takes (0) returns (1) { 281 | __tablesize(OPCODE_TABLE) // [opcode_table_len] 282 | __tablestart(OPCODE_TABLE) // [opcode_table_ptr, opcode_table_len] 283 | 0x00 // [mem_ptr, opcode_table_ptr, opcode_table_len] 284 | codecopy // [] 285 | 286 | [ARG1_CD_PTR] // [executable_cd_ptr] 287 | calldataload // [executable] 288 | [EXECUTABLE_PTR] // [mem_ptr, executable] 289 | mstore // [] 290 | 291 | [VIRTUAL_STACK_PTR] // [virtual_stack_depth] 292 | [VIRTUAL_STACK_PTR_PTR] // [virtual_stack_ptr_ptr, virtual_stack_depth] 293 | mstore // [] 294 | 295 | [ARG0_CD_PTR] // [program_input_cd_ptr] 296 | calldataload // [program_input] 297 | PUSH_NUMBER(INPUT_7) // [program_input, input_7] 298 | PUSH_NUMBER(INPUT_6) // [program_input, input_6, input_7] 299 | PUSH_NUMBER(INPUT_5) // [program_input, input_5, input_6, input_7] 300 | PUSH_NUMBER(INPUT_4) // [program_input, input_4, input_5, input_6, input_7] 301 | PUSH_NUMBER(INPUT_3) // [program_input, input_3, input_4, input_5, input_6, input_7] 302 | PUSH_NUMBER(INPUT_2) // [program_input, input_2, input_3, input_4, input_5, input_6, input_7] 303 | PUSH_NUMBER(INPUT_1) // [program_input, input_1, input_2, input_3, input_4, input_5, input_6, input_7] 304 | PUSH_NUMBER(INPUT_0) // [program_input, input_0, input_1, input_2, input_3, input_4, input_5, input_6, input_7] 305 | pop // [input_0, input_1, input_2, input_3, input_4, input_5, input_6, input_7] 306 | } 307 | 308 | /// ## Get Opcode 309 | /// 310 | /// ### Directives 311 | /// 312 | /// 1. Read the program counter from memory. 313 | /// 2. Add the executable offer to the program counter. 314 | /// 3. Read the opcode from memory. 315 | /// 4. Shift the opcode to the right by 248 bits. 316 | /// 5. Mask the opcode with seven. 317 | #define macro GET_OPCODE() = takes (0) returns (1) { 318 | [PC_PTR] // [program_counter_ptr] 319 | mload // [program_counter] 320 | [EXECUTABLE_PTR] // [executable_start, program_counter] 321 | add // [opcode_ptr] 322 | mload // [opcode_word] 323 | 0xf8 // [shift, opcode_word] 324 | shr // [opcode] 325 | [OPCODE_MOD] // [opcode_mod, opcode] 326 | swap1 // [opcode, opcode_mod] 327 | mod // [valid_opcode] 328 | } 329 | 330 | /// ## Increment Program Counter 331 | /// 332 | /// ### Directives 333 | /// 334 | /// 1. Read the program counter from memory. 335 | /// 2. Add one to the program counter. 336 | /// 3. Write the new program counter to memory. 337 | #define macro INCREMENT_PC() = takes (0) returns (0) { 338 | [PC_PTR] // [program_counter_ptr, opcode] 339 | dup1 // [program_counter_ptr, program_counter_ptr, opcode] 340 | mload // [program_counter, program_counter_ptr, opcode] 341 | 0x01 // [one, program_counter, program_counter_ptr, opcode] 342 | add // [new_program_counter, program_counter_ptr, opcode] 343 | swap1 // [program_counter_ptr, new_program_counter, opcode] 344 | mstore // [opcode] 345 | } 346 | 347 | /// ## Dispatch Opcode 348 | /// 349 | /// ### Directives 350 | /// 351 | /// 1. Shift the opcode to the left by five bits. 352 | /// 2. Read the opcode from the jumptable in memory. 353 | #define macro DISPATCH_OPCODE() = takes (1) returns (0) { 354 | 0x05 // [shift, opcode] 355 | shl // [op_dispatch_ptr] 356 | mload // [op_dispatch_dest] 357 | jump // [] 358 | } 359 | 360 | /// ## Decrement Virtual Stack Pointer 361 | /// 362 | /// ### Directives 363 | /// 364 | /// 1. Read the virtual stack pointer from memory. 365 | /// 2. Subtract one from the virtual stack pointer. 366 | /// 3. Write the new virtual stack pointer to memory. 367 | #define macro DEC_VIRTUAL_STACK_PTR() = takes (0) returns (0) { 368 | [VIRTUAL_STACK_PTR_PTR] // [virtual_stack_ptr_ptr] 369 | 0x01 // [one, virtual_stack_ptr_ptr] 370 | dup2 // [virtual_stack_ptr_ptr, one, virtual_stack_ptr_ptr] 371 | mload // [virtual_stack_ptr, one, virtual_stack_ptr_ptr] 372 | sub // [new_virtual_stack_ptr, virtual_stack_ptr_ptr] 373 | swap1 // [virtual_stack_ptr_ptr, new_virtual_stack_ptr] 374 | mstore // [] 375 | } 376 | 377 | /// ## Check the Program Output 378 | /// 379 | /// ### Directives 380 | /// 381 | /// 1. Checks if the result is equal to prime 0. 382 | /// 2. Checks if the result is equal to prime 1. 383 | /// 3. Checks if the result is equal to prime 2. 384 | /// 4. Accumulates the result of the three checks. 385 | /// 5. Checks if the stack is empty. 386 | /// 6. Accumulates the result of the two checks. 387 | #define macro CHECK_OUTPUT() = takes (1) returns (1) { 388 | // takes: // [result] 389 | dup1 dup1 // [result, result, result] 390 | [PRIME_0] // [prime_0, result, result, result] 391 | eq // [is_prime_0, result, result] 392 | 393 | swap1 // [result, is_prime_0, result] 394 | [PRIME_1] // [prime_1, result, is_prime_0, result] 395 | eq // [is_prime_1, is_prime_0, result] 396 | 397 | swap2 // [result, is_prime_1, is_prime_0] 398 | [PRIME_2] // [prime_2, result, is_prime_1, is_prime_0] 399 | eq // [is_prime_2, is_prime_1, is_prime_0] 400 | 401 | or or // [is_valid_prime] 402 | 403 | [VIRTUAL_STACK_PTR_PTR] // [virtual_stack_ptr_ptr, is_valid_prime] 404 | mload // [virtual_stack_ptr, is_valid_prime] 405 | iszero // [is_stack_empty, is_valid_prime] 406 | and // [is_valid_output] 407 | } 408 | 409 | // --- Utilities --- 410 | 411 | /// ## Generate Byte of Pseudorandom Number 412 | /// 413 | /// ### Directives 414 | /// 415 | /// 1. Extract a byte from the `seed` index `idx`. 416 | /// 2. Modulo the byte by the `MAX_VALUE`. 417 | /// 3. Add one to the result. 418 | /// 4. Store the result in memory. 419 | #define macro GEN_BYTE(idx) = takes (1) returns (1) { 420 | // takes: // [seed] 421 | [MAX_VALUE] // [mod, seed] 422 | dup2 // [seed, mod, seed] 423 | // [idx, seed, mod, seed] 424 | byte // [byte, mod, seed] 425 | mod // [rand, seed] 426 | 0x01 // [one, rand, seed] 427 | add // [rand, seed] 428 | // [idx, rand, seed] 429 | mstore8 // [seed] 430 | } 431 | 432 | /// ## Push Pseudorandom Number to Stack 433 | /// 434 | /// ### Directives 435 | /// 436 | /// 1. Extract a byte from the `program_input` index `idx`. 437 | #define macro PUSH_NUMBER(idx) = takes (1) returns (2) { 438 | // takes: // [program_input] 439 | dup1 // [program_input, program_input] 440 | // [idx, program_input, program_input] 441 | byte // [byte, program_input] 442 | swap1 // [program_input, byte] 443 | } 444 | 445 | // --- Jumptable --- 446 | 447 | /// ## Opcode Jumptable 448 | /// 449 | /// Maps opcodes to jumptable indices in memory. 450 | #define jumptable OPCODE_TABLE { 451 | op_halt // 0x0000 452 | op_add // 0x0020 453 | op_sub // 0x0040 454 | op_mul // 0x0060 455 | op_div // 0x0080 456 | op_swap1 // 0x00a0 457 | op_swap2 // 0x00c0 458 | op_swap3 // 0x00e0 459 | op_swap4 // 0x0100 460 | op_swap5 // 0x0120 461 | op_swap6 // 0x0140 462 | op_swap7 // 0x0160 463 | } 464 | 465 | EOF SacredGeometry.huff */ 466 | 467 | interface Challenge { 468 | function name() external pure returns (string memory); 469 | function generate(address) external pure returns (uint256); 470 | function verify(uint256 input, uint256 solution) external pure returns (bool); 471 | } 472 | 473 | contract SacredGeometryTest is Test { 474 | Challenge public challenge; 475 | 476 | // obtained with challenge.generate(address(uint160(uint256(keccak256(bytes("Sacred Geometry")))))) 477 | uint256 constant INPUT = 0x0205010104030306; 478 | 479 | function setUp() public { 480 | // obtained with `huffc -b SacredGeometry.huff` 481 | bytes memory deploymentBytecode = hex"61032780600a3d393df360003560e01c806306fdde031461002c5780632fa61cd81461004b57806341161b10146100bc5760006000fd5b60206000526f0f5361637265642047656f6d65747279602f5260606000f35b600435600681601f1a06600101601f53600681601e1a06600101601e53600681601d1a06600101601d53600681601c1a06600101601c53600681601b1a06600101601b53600681601a1a06600101601a5360068160191a0660010160195360068160181a0660010160185360206000f35b6101806101a76000396024356101a05260076101c0526004358060181a908060191a9080601a1a9080601b1a9080601c1a9080601d1a9080601e1a9080601f1a90505b610180516101a0015160f81c600c90066101808051600101905260051b51565b6101c060018151039052016100ff565b6101c060018151039052036100ff565b6101c060018151039052026100ff565b6101c060018151039052046100ff565b906100ff565b916100ff565b926100ff565b936100ff565b946100ff565b956100ff565b966100ff565b8080600d14906011149160131417176101c051151660005260206000f30000000000000000000000000000000000000000000000000000000000000189000000000000000000000000000000000000000000000000000000000000011f000000000000000000000000000000000000000000000000000000000000012f000000000000000000000000000000000000000000000000000000000000013f000000000000000000000000000000000000000000000000000000000000014f000000000000000000000000000000000000000000000000000000000000015f0000000000000000000000000000000000000000000000000000000000000165000000000000000000000000000000000000000000000000000000000000016b00000000000000000000000000000000000000000000000000000000000001710000000000000000000000000000000000000000000000000000000000000177000000000000000000000000000000000000000000000000000000000000017d0000000000000000000000000000000000000000000000000000000000000183"; 482 | 483 | // deploy the challenge contract 484 | address addr; 485 | assembly { 486 | addr := create(0, add(deploymentBytecode, 0x20), mload(deploymentBytecode)) 487 | } 488 | 489 | challenge = Challenge(address(addr)); 490 | } 491 | 492 | /// @dev sanity check to verify that the contract is deployed correctly 493 | function test_name() public { 494 | string memory name = challenge.name(); 495 | assertEq(keccak256(bytes(name)), keccak256(bytes("Sacred Geometry"))); 496 | } 497 | 498 | /// brute force fuzz run can occasionally find a solution 499 | /// forge-config: default.fuzz.runs = 1000000 500 | // function testFuzz_verify(uint256 solution) public { 501 | // try challenge.verify(INPUT, solution) returns (bool accepted) { 502 | // assertEq(accepted, false); 503 | // } catch { 504 | // // pass 505 | // } 506 | // } 507 | 508 | /// @dev uncomment to validate a solution 509 | // function test_verify_solution() public view { 510 | // uint256 solution = 0x3103291d2ac871df13000000000000000000000000000000000000000000000; 511 | // assert(challenge.verify(INPUT, solution)); 512 | // } 513 | 514 | function prove_verify(uint256 solution) public view { 515 | assert(!challenge.verify(INPUT, solution)); 516 | } 517 | } 518 | -------------------------------------------------------------------------------- /src/unsafe/ds-test/storage-unsafe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity ^0.8.17; 4 | import "ds-test/test.sol"; 5 | 6 | contract C is DSTest { 7 | mapping (uint => mapping (uint => uint)) maps; 8 | 9 | function proveMappingAccess(uint x, uint y) public { 10 | maps[y][0] = x; 11 | maps[x][0] = y; 12 | assert(maps[y][0] == maps[x][0]); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/unsafe/ds-test/synthetic-manybranch.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | import "forge-std/Test.sol"; 4 | 5 | 6 | function pathy(uint256 x) returns(bool) { 7 | uint256 acc = 1; 8 | 9 | // notice, all the values are primes. So the system has to figure 10 | // out the primal decomposition of the solution. Hence, there is a unique 11 | // solution (given that we cannot roll over due to low numbers) 12 | acc *= (x & 0xFF000000000000000000 > 0) ? uint256( 1) : uint256( 31); 13 | acc *= (x & 0x00FF0000000000000000 > 0) ? uint256( 3) : uint256( 37); 14 | acc *= (x & 0x0000FF00000000000000 > 0) ? uint256( 5) : uint256( 41); 15 | acc *= (x & 0x000000FF000000000000 > 0) ? uint256( 7) : uint256( 43); 16 | acc *= (x & 0x00000000FF0000000000 > 0) ? uint256( 11) : uint256( 47); 17 | acc *= (x & 0x0000000000FF00000000 > 0) ? uint256( 13) : uint256( 53); 18 | acc *= (x & 0x000000000000FF000000 > 0) ? uint256( 17) : uint256( 59); 19 | acc *= (x & 0x00000000000000FF0000 > 0) ? uint256( 19) : uint256( 61); 20 | acc *= (x & 0x0000000000000000FF00 > 0) ? uint256( 23) : uint256( 67); 21 | acc *= (x & 0x000000000000000000FF > 0) ? uint256( 29) : uint256( 71); 22 | 23 | // 31*3*5*7*47*13*59*19*67*71 24 | // = 10605495576585 25 | return acc != 10605495576585; 26 | } 27 | 28 | contract SyntheticManyBranch is Test { 29 | function prove_pAtHExPlOSion(uint256 x) external { 30 | assertTrue(pathy(x)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/unsafe/ds-test/withdraw.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.19; 2 | 3 | import "ds-test/test.sol"; 4 | import "src/common/erc20.sol"; 5 | 6 | contract Withdraw { 7 | receive() external payable {} 8 | 9 | function withdraw(uint password) public { 10 | require(password == 42, "Access denied!"); 11 | payable(msg.sender).transfer(address(this).balance); 12 | } 13 | } 14 | 15 | contract SolidityTestFail is DSTest { 16 | ERC20 token; 17 | Withdraw withdraw; 18 | 19 | function setUp() public { 20 | token = new ERC20("TKN", "T", 18); 21 | withdraw = new Withdraw(); 22 | } 23 | 24 | function proveFail_withdraw(uint guess) public { 25 | payable(address(withdraw)).transfer(1 ether); 26 | uint preBalance = address(this).balance; 27 | withdraw.withdraw(guess); 28 | uint postBalance = address(this).balance; 29 | assert(preBalance + 1 ether == postBalance); 30 | } 31 | 32 | // allow sending eth to the test contract 33 | receive() external payable {} 34 | } 35 | -------------------------------------------------------------------------------- /src/unsafe/ds-test/xor-magic.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.17; 3 | 4 | import {DSTest} from "ds-test/test.sol"; 5 | 6 | contract XorMagic { 7 | uint256 internal constant MAGIC = 0x69; 8 | 9 | uint256 internal flagA = 0; 10 | uint256 internal flagB = 0; 11 | 12 | function foo(uint256 x) external { 13 | assembly { 14 | if iszero(xor(x, MAGIC)) { 15 | sstore(flagA.slot, 0x01) 16 | } 17 | } 18 | } 19 | 20 | function bar(uint256 y) external { 21 | assembly { 22 | if iszero(xor(y, MAGIC)) { 23 | sstore(flagB.slot, 0x01) 24 | } 25 | } 26 | } 27 | 28 | function revertIfCracked() external view { 29 | bool res = true; 30 | assembly { 31 | if and(sload(flagA.slot), sload(flagB.slot)) { 32 | res := false 33 | } 34 | } 35 | assert(res); 36 | } 37 | } 38 | 39 | contract XorMagicTest is DSTest { 40 | XorMagic internal a; 41 | 42 | function setUp() public { 43 | a = new XorMagic(); 44 | } 45 | 46 | /// @dev Attempts to find a combination of `x` and `y` that will cause 47 | /// this test to revert. In order to do so, both `x` and `y` should 48 | /// be equal to `0x69`. 49 | function proveFuzz_cracked(uint256 x, uint256 y) public { 50 | a.foo(x); 51 | a.bar(y); 52 | a.revertIfCracked(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tools/halmos.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | contract_file="$1" ; shift 6 | contract_name="$1" ; shift 7 | fun_name="$1"; shift 8 | sig="$1"; shift 9 | ds_test="$1"; shift 10 | tout="$1"; shift 11 | memout="$1"; shift 12 | dump_smt="$1"; shift 13 | 14 | extra_params="" 15 | if [[ "$dump_smt" == "1" ]]; then 16 | extra_params="${extra_params} --dump-smt-queries" 17 | fi 18 | 19 | if [[ "${ds_test}" == "0" ]]; then 20 | out=$(runlim --real-time-limit="${tout}" --kill-delay=2 --space-limit="${memout}" halmos --function "${fun_name}" --contract "${contract_name}" --symbolic-storage --symbolic-msg-sender ${extra_params} "$@" 2>&1) 21 | elif [[ "${ds_test}" == "1" ]]; then 22 | out=$(runlim --real-time-limit="${tout}" --kill-delay=2 --space-limit="${memout}" halmos --function "${fun_name}" --contract "${contract_name}" ${extra_params} "$@" 2>&1) 23 | else 24 | echo "Called incorrectly" 25 | exit 1 26 | fi 27 | 28 | # Check if we emitted smt2 files. If so, copy them over to a 29 | # directory based on the contract file & name 30 | set +x 31 | if [[ "$dump_smt" == "1" ]] && [[ "$out" =~ "Generating SMT" ]]; then 32 | regexp="s/^Generating SMT queries in \\(.*\\)/\\1/" 33 | smtdir=$(echo "$out" | grep "Generating SMT" | sed -e "${regexp}") 34 | outdir="halmos-smt2/${contract_file}.${contract_name}/" 35 | mkdir -p "$dir" 36 | mv -f ${smtdir}/*.smt2 "${outdir}/" 37 | fi 38 | 39 | if [[ $out =~ "Counterexample: unknown" ]]; then 40 | echo "result: unknown" 41 | exit 0 42 | fi 43 | 44 | if [[ $out =~ "Counterexample (potentially invalid)" ]]; then 45 | echo "result: unknown" 46 | exit 0 47 | fi 48 | 49 | if [[ $out =~ "Encountered symbolic CALLDATALOAD offset" ]]; then 50 | echo "result: unknown" 51 | exit 0 52 | fi 53 | 54 | if [[ $out =~ "paths have not been fully explored due to the loop unrolling bound" ]]; then 55 | echo "result: unknown" 56 | exit 0 57 | fi 58 | 59 | if [[ $out =~ "Traceback" ]]; then 60 | exit 1 61 | fi 62 | 63 | if [[ $out =~ "Encountered symbolic memory offset" ]]; then 64 | echo "result: unknown" 65 | exit 0 66 | fi 67 | 68 | if [[ $out =~ "[FAIL]" ]]; then 69 | echo "result: unsafe" 70 | exit 0 71 | fi 72 | 73 | if [[ $out =~ "[PASS]" ]]; then 74 | echo "result: safe" 75 | exit 0 76 | fi 77 | 78 | exit 1 79 | -------------------------------------------------------------------------------- /tools/halmos_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ver="0.0.8" 4 | echo "${ver}" 5 | -------------------------------------------------------------------------------- /tools/hevm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | contract_file="$1" ; shift 6 | contract_name="$1" ; shift 7 | fun_name="$1"; shift 8 | sig="$1"; shift 9 | ds_test="$1"; shift 10 | tout="$1"; shift 11 | memout="$1"; shift 12 | dump_smt="$1"; shift 13 | 14 | extra_params="" 15 | if [[ "$dump_smt" == "1" ]]; then 16 | extra_params="${extra_params} --smtdebug" 17 | rm -f ./*.smt2 18 | fi 19 | 20 | 21 | HEVM_BIN=hevm 22 | 23 | rm -f ./*.smt2 24 | 25 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 26 | source "$SCRIPT_DIR/utils.sh" 27 | 28 | if [[ "${ds_test}" == "0" ]]; then 29 | code=$(get_runtime_bytecode "${contract_file}" "${contract_name}") 30 | out=$(runlim --real-time-limit="${tout}" --space-limit="${memout}" --kill-delay=2 "$HEVM_BIN" symbolic --code "${code}" --sig "${sig}" ${extra_params} "$@" 2>&1) 31 | elif [[ "${ds_test}" == "1" ]]; then 32 | out=$(runlim --real-time-limit="${tout}" --space-limit="${memout}" --kill-delay=2 "$HEVM_BIN" test --match "${contract_file}.*${contract_name}.*${fun_name}" --verbose 2 ${extra_params} "$@" 2>&1) 33 | else 34 | echo "Called incorrectly" 35 | exit 1 36 | fi 37 | 38 | # Check if we emitted smt2 files. If so, copy them over to a 39 | # directory based on the contract file & name 40 | if [[ "$dump_smt" == "1" ]]; then 41 | shopt -s nullglob 42 | set -- *.smt2 43 | if [ "$#" -gt 0 ]; then 44 | dir="hevm-smt2/${contract_file}.${contract_name}/" 45 | mkdir -p "$dir" 46 | mv -f ./*.smt2 "$dir/" 47 | fi 48 | fi 49 | 50 | set +x 51 | 52 | if [[ $out =~ "hevm was only able to partially explore the given contract" ]]; then 53 | echo "unknown" 54 | exit 0 55 | fi 56 | 57 | if [[ $out =~ "No reachable assertion violations, but all branches reverted" ]]; then 58 | echo "result: safe" 59 | exit 0 60 | fi 61 | 62 | if [[ $out =~ "[FAIL]" ]]; then 63 | echo "result: unsafe" 64 | exit 0 65 | fi 66 | 67 | if [[ $out =~ "[PASS]" ]]; then 68 | echo "result: safe" 69 | exit 0 70 | fi 71 | 72 | if [[ $out =~ "QED: No reachable property violations discovered" ]]; then 73 | echo "result: safe" 74 | exit 0 75 | fi 76 | 77 | if [[ $out =~ "Discovered the following counterexamples" ]]; then 78 | echo "result: unsafe" 79 | exit 0 80 | fi 81 | 82 | if [[ $out =~ "Could not determine reachability of the following end states" ]]; then 83 | echo "result: unknown" 84 | exit 0 85 | fi 86 | 87 | if [[ $out =~ "cannot delegateCall with symbolic target or context" ]]; then 88 | echo "result: unknown" 89 | exit 0 90 | fi 91 | 92 | exit 1 93 | -------------------------------------------------------------------------------- /tools/hevm_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ver=$(hevm version 2>/dev/null) 4 | verclean=$(echo "${ver}" | awk '{print $1}') 5 | echo "${verclean}" 6 | -------------------------------------------------------------------------------- /tools/kevm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | contract_file="$1" ; shift 6 | contract_name="$1" ; shift 7 | fun_name="$1"; shift 8 | ds_test="$1"; shift 9 | tout="$1"; shift 10 | 11 | contract_dir="$(dirname "${contract_file}")" 12 | contract_k="${contract_dir}/.k-artifacts/bin-runtime.k" 13 | mkdir -p "$(dirname "${contract_k}")" 14 | 15 | doalarm -t alarm "${tout}" kevm solc-to-k "${contract_file}" "${contract_name}" > "${contract_k}" 16 | cat "${contract_k}" 17 | -------------------------------------------------------------------------------- /tools/kontrol.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | 5 | contract_file="$1" ; shift 6 | contract_name="$1" ; shift 7 | fun_name="$1"; shift 8 | sig="$1"; shift 9 | ds_test="$1"; shift 10 | tout="$1"; shift 11 | memout="$1"; shift 12 | 13 | rm -f ./*.smt2 14 | 15 | # kontrol can't do symbolic storage yet 16 | if [[ "${ds_test}" == "0" ]]; then 17 | echo "result: unknown" 18 | exit 0 19 | fi 20 | 21 | out=$(runlim --real-time-limit="${tout}" --kill-delay=2 --space-limit="${memout}" kontrol prove --counterexample-information --match-test "${contract_name}.${fun_name}" "$@" 2>&1) 22 | 23 | # Check if we emitted smt2 files. If so, copy them over to a 24 | # directory based on the contract file & name 25 | shopt -s nullglob 26 | set -- *.smt2 27 | if [ "$#" -gt 0 ]; then 28 | dir="halmos-smt2/${contract_file}.${contract_name}/" 29 | mkdir -p "$dir" 30 | mv -f ./*.smt2 "$dir/" 31 | fi 32 | 33 | set +x 34 | 35 | if [[ $out =~ "PROOF PASSED" ]]; then 36 | echo "result: safe" 37 | exit 0 38 | fi 39 | 40 | if [[ $out =~ "PROOF FAILED" ]] && [[ $out =~ "Model:" ]]; then 41 | echo "result: unsafe" 42 | exit 0 43 | fi 44 | 45 | if [[ $out =~ "PROOF FAILED" ]] && [[ $out =~ "Failed to generate a model" ]]; then 46 | echo "result: unknown" 47 | exit 0 48 | fi 49 | 50 | exit 1 51 | -------------------------------------------------------------------------------- /tools/kontrol_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ver=$(kontrol version 2>/dev/null) 4 | verclean=$(echo "${ver}" | awk '{print $3}') 5 | echo "${verclean}" 6 | -------------------------------------------------------------------------------- /tools/test-kevm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | contract_file="$1" ; shift 6 | contract_name="$1" ; shift 7 | 8 | contract_dir="$(dirname "${contract_file}")" 9 | contract_k="${contract_dir}/.k-artifacts/bin-runtime.k" 10 | mkdir -p "$(dirname "${contract_k}")" 11 | 12 | kevm solc-to-k "${contract_file}" "${contract_name}" > "${contract_k}" 13 | cat "${contract_k}" 14 | -------------------------------------------------------------------------------- /tools/utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # takes a path to a solidity file and a contract name and returns the runtime bytecode 4 | get_runtime_bytecode() { 5 | solidity_file=$1 6 | contract_name=$2 7 | filename=$(basename "${solidity_file}") 8 | json_file="out/${filename}/${contract_name}.json" 9 | jq .deployedBytecode.object -r "${json_file}" 10 | } 11 | --------------------------------------------------------------------------------