|
1 | 1 | # `hevm equivalence` |
2 | 2 |
|
3 | | -``` |
4 | | -Usage: hevm equivalence --code-a TEXT --code-b TEXT [--sig TEXT] |
5 | | - [--arg STRING]... [--calldata TEXT] |
6 | | - [--smttimeout NATURAL] [--max-iterations INTEGER] |
7 | | - [--solver TEXT] [--smtoutput] [--smtdebug] [--debug] |
8 | | - [--trace] [--ask-smt-iterations INTEGER] |
9 | | - [--num-cex-fuzz INTEGER] |
10 | | - [--loop-detection-heuristic LOOPHEURISTIC] |
11 | | - [--abstract-arithmetic] [--abstract-memory] |
12 | | -
|
13 | | -Available options: |
14 | | - -h,--help Show this help text |
15 | | - --code-a TEXT Bytecode of the first program |
16 | | - --code-b TEXT Bytecode of the second program |
17 | | - --sig TEXT Signature of types to decode / encode |
18 | | - --arg STRING Values to encode |
19 | | - --calldata TEXT Tx: calldata |
20 | | - --smttimeout NATURAL Timeout given to SMT solver in seconds (default: 300) |
21 | | - --max-iterations INTEGER Number of times we may revisit a particular branching |
22 | | - point. Default is 5. Setting to -1 allows infinite looping |
23 | | - --solver TEXT Used SMT solver: z3 (default), cvc5, or bitwuzla |
24 | | - --smtoutput Print verbose smt output |
25 | | - --smtdebug Print smt queries sent to the solver |
26 | | - --debug Debug printing of internal behaviour |
27 | | - --trace Dump trace |
28 | | - --ask-smt-iterations INTEGER |
29 | | - Number of times we may revisit a particular branching |
30 | | - point before we consult the smt solver to check |
31 | | - reachability (default: 1) (default: 1) |
32 | | - --num-cex-fuzz INTEGER Number of fuzzing tries to do to generate a |
33 | | - counterexample (default: 3) (default: 3) |
34 | | - --loop-detection-heuristic LOOPHEURISTIC |
35 | | - Which heuristic should be used to determine if we are |
36 | | - in a loop: StackBased (default) or Naive |
37 | | - (default: StackBased) |
| 3 | +```plain |
| 4 | +Usage: hevm equivalence [--code-a TEXT] [--code-b TEXT] [--code-a-file STRING] |
| 5 | + [--code-b-file STRING] [--sig TEXT] [--arg STRING]... |
| 6 | + [--calldata TEXT] [--smttimeout NATURAL] |
| 7 | + [--max-iterations INTEGER] [--solver TEXT] |
| 8 | + [--num-solvers NATURAL] ... |
38 | 9 | ``` |
39 | 10 |
|
40 | 11 | Symbolically execute both the code given in `--code-a` and `--code-b` and try |
41 | | -to prove equivalence between their outputs and storages. Extracting bytecode |
42 | | -from solidity contracts can be done via, e.g.: |
| 12 | +to prove equivalence between their outputs and storages. For a full listing of |
| 13 | +options, see `hevm equivalence --help`. For common options, see |
| 14 | +[here](./common-options.md). |
43 | 15 |
|
| 16 | +## Simple example usage |
| 17 | + |
| 18 | +```shell |
| 19 | +$ solc --bin-runtime "contract1.sol" | tail -n1 > a.bin |
| 20 | +$ solc --bin-runtime "contract2.sol" | tail -n1 > b.bin |
| 21 | +$ hevm equivalence --code-a-file a.bin --code-b-file b.bin |
44 | 22 | ``` |
45 | | -hevm equivalence \ |
46 | | - --code-a $(solc --bin-runtime "contract1.sol" | tail -n1) \ |
47 | | - --code-b $(solc --bin-runtime "contract2.sol" | tail -n1) |
48 | | -``` |
| 23 | + |
| 24 | +## Calldata size limits |
| 25 | + |
49 | 26 | If `--sig` is given, calldata is assumed to take the form of the function |
50 | 27 | given. If `--calldata` is provided, a specific, concrete calldata is used. If |
51 | 28 | neither is provided, a fully abstract calldata of at most `2**64` byte is |
52 | 29 | assumed. Note that a `2**64` byte calldata would go over the gas limit, and |
53 | | -hence should cover all meaningful cases. |
| 30 | +hence should cover all meaningful cases. You can limit the buffer size via |
| 31 | +`--max-buf-size`, which sets the exponent of the size, i.e. 10 would limit the |
| 32 | +calldata to `2**10` bytes. |
| 33 | + |
| 34 | +## What constitutes equivalence |
| 35 | + |
| 36 | +The equivalence checker considers two contracts equivalent if given the |
| 37 | +same calldata they: |
| 38 | +- return the same value |
| 39 | +- have the same storage |
| 40 | +- match on the success/failure of the execution |
| 41 | +Importantly, logs are *not* considered in the equivalence check. Hence, |
| 42 | +it is possible that two contracts are considered equivalent by `hevm equivalence` but |
| 43 | +they emit different log items. Furthermore, gas is explicitly not considered, |
| 44 | +as in many cases, the point of the equivalence check is to ensure that the |
| 45 | +contracts are functionally equivalent, but one of them is more gas efficient. |
| 46 | + |
| 47 | +For example, two contracts that are: |
| 48 | + |
| 49 | +``` |
| 50 | +PUSH1 3 |
| 51 | +``` |
| 52 | + |
| 53 | +And |
| 54 | + |
| 55 | +``` |
| 56 | +PUSH1 4 |
| 57 | +``` |
| 58 | + |
| 59 | +Are considered *equivalent*, because they don't put anything in the return |
| 60 | +data, are not different in their success/fail attribute, and don't touch |
| 61 | +storage. However, these two are considered different: |
| 62 | + |
| 63 | +``` |
| 64 | +PUSH1 3 |
| 65 | +PUSH1 0x20 |
| 66 | +MSTORE |
| 67 | +PUSH1 0x40 |
| 68 | +PUSH1 0x00 |
| 69 | +RETURN |
| 70 | +``` |
| 71 | + |
| 72 | +and: |
| 73 | + |
| 74 | + |
| 75 | +``` |
| 76 | +PUSH1 4 |
| 77 | +PUSH1 0x20 |
| 78 | +MSTORE |
| 79 | +PUSH1 0x40 |
| 80 | +PUSH1 0x00 |
| 81 | +RETURN |
| 82 | +``` |
| 83 | + |
| 84 | +Since one of them returns a 3 and the other a 4. We also consider contracts different when |
| 85 | +they differ in success/fail. So these two contracts: |
| 86 | + |
| 87 | +``` |
| 88 | +PUSH1 0x00 |
| 89 | +PUSH1 0x00 |
| 90 | +RETURN |
| 91 | +``` |
| 92 | + |
| 93 | +and: |
| 94 | + |
| 95 | +``` |
| 96 | +PUSH1 0x00 |
| 97 | +PUSH1 0x00 |
| 98 | +REVERT |
| 99 | +``` |
| 100 | + |
| 101 | +Are considered different, as one of them reverts (i.e. fails) and the other |
| 102 | +succeeds. |
| 103 | + |
| 104 | +## Creation code equivalence |
| 105 | + |
| 106 | +If you want to check the equivalence of not just the runtime code, but also the |
| 107 | +creation code of two contracts, you can use the `--creation` flag. For example |
| 108 | +these two contracts: |
| 109 | + |
| 110 | +```solidity |
| 111 | +contract C { |
| 112 | + uint private immutable NUMBER; |
| 113 | + constructor(uint a) { |
| 114 | + NUMBER = 2; |
| 115 | + } |
| 116 | + function stuff(uint b) public returns (uint256) { |
| 117 | + unchecked{return 2+NUMBER+b;} |
| 118 | + } |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +And: |
| 123 | + |
| 124 | +```solidity |
| 125 | +contract C { |
| 126 | + uint private immutable NUMBER; |
| 127 | + constructor(uint a) { |
| 128 | + NUMBER = 4; |
| 129 | + } |
| 130 | + function stuff(uint b) public returns (uint256) { |
| 131 | + unchecked {return NUMBER+b;} |
| 132 | + } |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +Will compare equal when compared with `--create` flag: |
| 137 | + |
| 138 | +```shell |
| 139 | +solc --bin a.sol | tail -n1 > a.bin |
| 140 | +solc --bin b.sol | tail -n1 > b.bin |
| 141 | +cabal run exe:hevm equivalence -- --code-a-file a.bin --code-b-file b.bin --create |
| 142 | +``` |
| 143 | + |
| 144 | +Notice that we used `--bin` and not `--bin-runtime` for solc here. Also note that |
| 145 | +in case `NUMBER` is declared `public`, the two contracts will not be considered |
| 146 | +equivalent, since solidity will generate a getter for `NUMBER`, which will |
| 147 | +return 2/4 respectively. |
| 148 | + |
| 149 | +## Further reading |
| 150 | + |
| 151 | +For a tutorial on how to use `hevm equivalence`, see the [equivalence checking |
| 152 | +tutorial](symbolic-execution-tutorial.html). |
0 commit comments