hevm 0.24 → 0.41.0
raw patch · 41 files changed
+7989/−3424 lines, 41 filesdep +cborgdep +freedep +sbvdep −base64-bytestringdep −ghci-prettydep −readlinedep ~QuickCheckdep ~abstract-pardep ~aesonnew-uploaderPVP ok
version bump matches the API change (PVP)
Dependencies added: cborg, free, sbv, semver-range, witherable
Dependencies removed: base64-bytestring, ghci-pretty, readline
Dependency ranges changed: QuickCheck, abstract-par, aeson, ansi-wl-pprint, async, base, base16-bytestring, binary, brick, bytestring, cereal, containers, cryptonite, data-dword, deepseq, directory, fgl, filepath, haskeline, here, lens, lens-aeson, megaparsec, memory, monad-par, mtl, multiset, operational, optparse-generic, process, quickcheck-text, regex-tdfa, restless-git, rosezipper, s-cargot, scientific, temporary, text, text-format, time, transformers, unordered-containers, vector, vty, wreq
API changes (from Hackage documentation)
Files
- CHANGELOG.md +158/−0
- ethjet/blake2.cc +65/−0
- ethjet/blake2.h +17/−0
- ethjet/ethjet-ff.cc +278/−0
- ethjet/ethjet-ff.h +18/−0
- ethjet/ethjet.c +34/−0
- ethjet/ethjet.h +4/−0
- hevm-cli/hevm-cli.hs +642/−143
- hevm.cabal +121/−99
- run-blockchain-tests +136/−0
- run-consensus-tests +75/−47
- src/EVM.hs +2603/−1713
- src/EVM/ABI.hs +191/−58
- src/EVM/Concrete.hs +48/−77
- src/EVM/Dapp.hs +16/−17
- src/EVM/Debug.hs +4/−6
- src/EVM/Demand.hs +13/−0
- src/EVM/Dev.hs +32/−3
- src/EVM/Emacs.hs +111/−34
- src/EVM/Exec.hs +20/−8
- src/EVM/Facts.hs +16/−12
- src/EVM/Facts/Git.hs +3/−5
- src/EVM/FeeSchedule.hs +63/−6
- src/EVM/Fetch.hs +93/−21
- src/EVM/Flatten.hs +32/−14
- src/EVM/Format.hs +92/−89
- src/EVM/Keccak.hs +3/−67
- src/EVM/Op.hs +94/−1
- src/EVM/Patricia.hs +232/−0
- src/EVM/RLP.hs +79/−0
- src/EVM/Solidity.hs +154/−54
- src/EVM/Stepper.hs +68/−44
- src/EVM/StorageLayout.hs +6/−37
- src/EVM/SymExec.hs +317/−0
- src/EVM/Symbolic.hs +300/−0
- src/EVM/TTY.hs +515/−482
- src/EVM/Transaction.hs +92/−0
- src/EVM/Types.hs +130/−16
- src/EVM/UnitTest.hs +252/−223
- src/EVM/VMTest.hs +405/−94
- test/test.hs +457/−54
@@ -1,5 +1,163 @@ # hevm changelog +## 0.41.0 - 2020-08-19++### Changed++- Switched to [PVP](https://github.com/haskell/pvp/blob/master/pvp-faq.md) for version control, starting now at `0.41.0` (MAJOR.MAJOR.MINOR).+- z3 updated to 4.8.7+- Generate more interesting values in property based testing, + and implement proper shrinking for all abi values.+- Fixed soundness bug when using KECCAK or SHA256 opcode/precompile+- Fixed an issue in debug mode where backstepping could cause path information to be forgotten+- Ensure that pathconditions are consistent when branching, and end the execution with VMFailure: DeadPath if this is not the case+- Fixed a soundness bug where nonzero jumpconditions were assumed to equal one.+- default `--smttimeout` changed from unlimited to 20 seconds+- `hevm symbolic --debug` now respects `--max-iterations`++### Added++- `hevm exec --trace` flag to dump a trace+- Faster backstepping in interactive mode by saving multiple snapshot states.+- Support for symbolic storage for multiple contracts++## 0.40 - 2020-07-22++- hevm is now capable of symbolic execution!++### Changed+As a result, the types of several registers of the EVM have changed to admit symbolic values as well as concrete ones.++ - state.stack: `Word` -> `SymWord`.+ - state.memory: `ByteString` -> `[SWord 8]`.+ - state.callvalue: `W256` -> `SymWord`.+ - state.caller: `Addr` -> `SAddr`.+ - state.returndata: `ByteString` -> `[SWord 8]`.+ - state.calldata: `ByteString` -> `([SWord 8], (SWord 32))`. The first element is a list of symbolic bytes, the second is the length of calldata. We have `fst calldata !! i .== 0` for all `snd calldata < i`.+ + - tx.value: `W256` -> `SymWord`.+ + - contract.storage: `Map Word Word` -> `Storage`, defined as:+```hs+data Storage+ = Concrete (Map Word SymWord)+ | Symbolic (SArray (WordN 256) (WordN 256))+ deriving (Show)+```++### Added+New cli commands:+ - `hevm symbolic`: search for assertion violations, or step through a symbolic execution in debug mode.+ - `hevm equivalence`: compare two programs for equivalence.++See the README for details on usage.++The new module `EVM.SymExec` exposes several library functions dealing with symbolic execution.+In particular, + - `SymExec.interpret`: implements an operational monad script similar to `TTY.interpret` and `Stepper.interpret`, but returns a list of final VM states rather than a single VM.+ - `SymExec.verify`: takes a prestate and a postcondition, symbolically executes the prestate and checks that all final states matches the postcondition.++### Removed++The concrete versions of a lot of arithmetic operations, replaced with their more general symbolic counterpart.+++## 0.39 - 2020-07-13+ - Exposes abi encoding to cli+ - Added cheat code `hevm.store(address a, bytes32 location, bytes32 value)`+ - Removes `ExecMode`, always running as `ExecuteAsBlockchainTest`. This means that `hevm exec` now finalizes transactions as well.+ - `--code` is now entirely optional. Not supplying it returns an empty contract, or whatever is stored in `--state`.++## 0.38 - 2020-04-23+ - Exposes metadata stripping of bytecode to the cli: `hevm strip-metadata --code X`. [357](https://github.com/dapphub/dapptools/pull/357).+ - Fixes a bug in the srcmap parsing introduced in 0.37 [356](https://github.com/dapphub/dapptools/pull/356).+ - Fixes a bug in the abi-encoding of `bytes` with size > 32[358](https://github.com/dapphub/dapptools/pull/358).++## 0.37 - 2020-03-24+ - Sourcemap parser now admits `solc-0.6.0` compiled `.sol.json` files.++## 0.36 - 2020-01-07+ - Implement Istanbul support [318](https://github.com/dapphub/dapptools/pull/318)+ - Fix a bug introduced in [280](https://github.com/dapphub/dapptools/pull/280) of rlp encoding of transactions and sender address [320](https://github.com/dapphub/dapptools/pull/320/).+ - Make InvalidTx a fatal error for vm tests and ci.+ - Suport property based testing in unit tests. [313](https://github.com/dapphub/dapptools/pull/313) Arguments to test functions are randomly generated based on the function abi. Fuzz tests are not present in the graphical debugger.+ - Added flags `--replay` and `--fuzz-run` to `hevm dapp-test`, allowing for particular fuzz run cases to be rerun, or for configuration of how many fuzz tests are run.+ - Correct gas readouts for unit tests+ - Prevent crash when trying to jump to next source code point if source code is missing++## 0.35 - 2019-11-02+ - Merkle Patricia trie support [280](https://github.com/dapphub/dapptools/pull/280)+ - RLP encoding and decoding functions [280](https://github.com/dapphub/dapptools/pull/280)+ - Extended support for Solidity ABI encoding [259](https://github.com/dapphub/dapptools/pull/259)+ - Bug fixes surrounding unit tests and gas accounting (https://github.com/dapphub/dapptools/commit/574ef401d3e744f2dcf994da056810cf69ef84fe, https://github.com/dapphub/dapptools/commit/5257574dd9df14edc29410786b75e9fb9c59069f)++## 0.34 - 2019-08-28+ - handle new solc bzzr metadata in codehash for source map+ - show VM hex outputs as hexadecimal+ - rpc defaults to latest block+ - `hevm interactive`:+ - fix rpc fetch+ - scrollable memory pane+ - Fix regression in VMTest compliance.+ - `hevm exec` ergonomics:+ - Allow code/calldata prefixed with 0x+ - create transactions with specific caller nonce+ - interactive help pane+ - memory pane scrolling++## 0.33 - 2019-08-06+ - Full compliance with the [General State Tests][245] (with the+ BlockchainTest format), using the Yellow and Jello papers as+ reference, for Constantinople Fix (aka Petersburg). Including:+ - full precompile support+ - correct substate accounting, including touched accounts,+ selfdestructs and refunds+ - memory read/write semantics+ - many gas cost corrections+ - Show more information for non solc bytecode in interactive view+ (trace and storage)+ - Help text for all cli options+ - Enable `--debug` flag in `hevm dapp-test`++[245]: https://github.com/dapphub/dapptools/pull/245++## 0.32 - 2019-06-14+ - Fix dapp-test [nonce initialisation bug][224]++[224]: https://github.com/dapphub/dapptools/pull/224++## 0.31 - 2019-05-29+ - Precompiles: SHA256, RIPEMD, IDENTITY, MODEXP, ECADD, ECMUL,+ ECPAIRING, MODEXP+ - Show the hevm version with `hevm version`+ - Interactive mode:+ - no longer exits on reaching halt+ - new shortcuts: 'a' / 'e' for start / end+ - allow returning to test picker screen+ - Exact integer formatting in dapp-test and tty++## 0.30 - 2019-05-09+ - Adjustable verbosity level for `dapp-test` with `--verbose={0,1,2}`+ - Working stack build++## 0.29 - 2019-04-03+ - Significant jump in compliance with client tests+ - Add support for running GeneralStateTests++## 0.28 - 2019-03-22+ - Fix delegatecall gas metering, as reported in+ https://github.com/dapphub/dapptools/issues/34++## 0.27 - 2019-02-06+ - Fix [hevm flatten issue](https://github.com/dapphub/dapptools/issues/127)+ related to SemVer ranges in Solidity version pragmas++## 0.26 - 2019-02-05+ - Format Solidity Error(string) messages in trace++## 0.25 - 2019-02-04+ - Add SHL, SHR and SAR opcodes+ ## 0.24 - 2019-01-23 - Fix STATICCALL for precompiled contracts - Assume Solidity 0.5.2 in tests
@@ -0,0 +1,65 @@+#include <cstdint>+#include "blake2.h"++#define ROTR64(x, y) (((x) >> (y)) ^ ((x) << (64 - (y))))++#define B2B_G(a, b, c, d, x, y) \+ v[a] = v[a] + v[b] + x; \+ v[d] = ROTR64(v[d] ^ v[a], 32); \+ v[c] = v[c] + v[d]; \+ v[b] = ROTR64(v[b] ^ v[c], 24); \+ v[a] = v[a] + v[b] + y; \+ v[d] = ROTR64(v[d] ^ v[a], 16); \+ v[c] = v[c] + v[d]; \+ v[b] = ROTR64(v[b] ^ v[c], 63);++static const uint64_t blake2b_iv[8] = {+ 0x6A09E667F3BCC908, 0xBB67AE8584CAA73B,+ 0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1,+ 0x510E527FADE682D1, 0x9B05688C2B3E6C1F,+ 0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179+};++static const uint8_t sigma[10][16] = {+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },+ { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },+ { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },+ { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },+ { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },+ { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },+ { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },+ { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },+ { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },+ { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }+};++void blake2b_compress(uint64_t *h, uint64_t *m, uint64_t *t, char f, uint32_t rounds) {+ uint32_t i;+ uint64_t v[16];++ for (i = 0; i < 8; i++) {+ v[i] = h[i];+ v[i+8] = blake2b_iv[i];+ }++ v[12] ^= t[0];+ v[13] ^= t[1];++ if (f) v[14] = ~v[14];++ int index;+ for (i = 0; i < rounds; i++) {+ index = i % 10;+ B2B_G( 0, 4, 8, 12, m[sigma[index][ 0]], m[sigma[index][ 1]]);+ B2B_G( 1, 5, 9, 13, m[sigma[index][ 2]], m[sigma[index][ 3]]);+ B2B_G( 2, 6, 10, 14, m[sigma[index][ 4]], m[sigma[index][ 5]]);+ B2B_G( 3, 7, 11, 15, m[sigma[index][ 6]], m[sigma[index][ 7]]);+ B2B_G( 0, 5, 10, 15, m[sigma[index][ 8]], m[sigma[index][ 9]]);+ B2B_G( 1, 6, 11, 12, m[sigma[index][10]], m[sigma[index][11]]);+ B2B_G( 2, 7, 8, 13, m[sigma[index][12]], m[sigma[index][13]]);+ B2B_G( 3, 4, 9, 14, m[sigma[index][14]], m[sigma[index][15]]);+ }++ for (i = 0; i < 8; ++i)+ h[i] ^= v[i] ^ v[i+8];+}
@@ -0,0 +1,17 @@+#ifndef BLAKE2_H+#define BLAKE2_H++#include <stdint.h>++#ifdef __cplusplus+extern "C" {+#endif++void+blake2b_compress(uint64_t *h, uint64_t *m, uint64_t *t, char f, uint32_t rounds);++#ifdef __cplusplus+}+#endif++#endif
@@ -0,0 +1,278 @@+#include "ethjet.h"++#include <gmp.h>+#include <stdio.h>++#include <libff/algebra/fields/bigint.hpp>+#include <libff/algebra/curves/alt_bn128/alt_bn128_init.hpp>+#include <libff/algebra/curves/alt_bn128/alt_bn128_g1.hpp>+#include <libff/algebra/curves/alt_bn128/alt_bn128_g2.hpp>+#include <libff/algebra/curves/alt_bn128/alt_bn128_pairing.hpp>+#include <libff/common/profiling.hpp>++using namespace libff;++namespace ethjet_ff {+ void init() {+ libff::inhibit_profiling_info = true;+ libff::inhibit_profiling_counters = true;+ init_alt_bn128_params();+ }++ // for loading an element of F_q (a coordinate of G_1)+ // consumes 32 bytes+ alt_bn128_Fq read_Fq_element (uint8_t *in) {+ mpz_t x_data;+ mpz_init(x_data);+ mpz_import(x_data, 32, 1, sizeof(in[0]), 1, 0, in);++ mpz_t q;+ mpz_init(q);+ alt_bn128_modulus_q.to_mpz(q);+ const mp_size_t limbs = alt_bn128_q_limbs;++ if (mpz_cmp(x_data, q) >= 0)+ throw 0;++ return Fp_model<limbs, alt_bn128_modulus_q>(bigint<limbs>(x_data));+ }++ // for loading an element of F_{q^2} (a coordinate of G_2)+ // consumes 64 bytes+ alt_bn128_Fq2 read_Fq2_element (uint8_t *in) {+ // suprising "big-endian" encoding+ alt_bn128_Fq x0 = read_Fq_element(in+32);+ alt_bn128_Fq x1 = read_Fq_element(in);++ mpz_t q;+ mpz_init(q);+ alt_bn128_modulus_q.to_mpz(q);++ return Fp2_model<alt_bn128_q_limbs, alt_bn128_modulus_q>(x0, x1);+ }++ // for loading an element of F_r (a scalar for G_1)+ // consumes 32 bytes+ alt_bn128_Fr read_Fr_element (uint8_t *in) {+ mpz_t x_data;+ mpz_init(x_data);+ mpz_import(x_data, 32, 1, sizeof(in[0]), 1, 0, in);++ mpz_t r;+ mpz_init(r);+ alt_bn128_modulus_r.to_mpz(r);+ const mp_size_t limbs = alt_bn128_r_limbs;++ return Fp_model<limbs, alt_bn128_modulus_r>(bigint<limbs>(x_data));+ }++ // for loading a point in G_1+ // consumes 64 bytes+ alt_bn128_G1 read_G1_point (uint8_t *in) {+ alt_bn128_Fq ax = read_Fq_element(in);+ alt_bn128_Fq ay = read_Fq_element(in+32);+ alt_bn128_G1 a;+ // create curve point from affine coordinates+ // the point at infinity (0,0) is a special case+ if (ax.is_zero() && ay.is_zero()) {+ a = alt_bn128_G1::G1_zero;+ }+ else {+ a = alt_bn128_G1(ax, ay, alt_bn128_Fq::one());+ }+ if (! a.is_well_formed()) {+ throw 0;+ }+ return a;+ }++ // for loading a point in G_2+ // consumes 128 bytes+ alt_bn128_G2 read_G2_point (uint8_t *in) {+ alt_bn128_Fq2 ax = read_Fq2_element(in);+ alt_bn128_Fq2 ay = read_Fq2_element(in+64);+ alt_bn128_G2 a;+ // create curve point from affine coordinates+ // the point at infinity (0,0) is a special case+ if (ax.is_zero() && ay.is_zero()) {+ a = alt_bn128_G2::G2_zero;+ return a;+ }+ a = alt_bn128_G2(ax, ay, alt_bn128_Fq2::one());+ if (! a.is_well_formed()) {+ throw 0;+ }+ // additionally check that the element has the right order+ if (-alt_bn128_Fr::one() * a + a != alt_bn128_G2::G2_zero) {+ throw 0;+ }+ return a;+ }++ // writes an element of Fq+ // produces 32 bytes+ void write_Fq_element(uint8_t *out, alt_bn128_Fq x) {+ mpz_t x_data;+ size_t x_size;+ mpz_init(x_data);++ x.as_bigint().to_mpz(x_data);+ uint8_t *x_arr = (uint8_t *)mpz_export(NULL, &x_size, 1, 1, 1, 0, x_data);+ if (x_size > 32) {+ throw 0;+ }+ // copy the result to the output buffer+ // with padding+ for (int i = 1; i <= 32; i++) {+ if (i <= x_size)+ out[32-i] = x_arr[x_size-i];+ else+ out[32-i] = 0;+ }+ return;+ }++ // writes an element of F_{q^2}+ // produces 64 bytes+ void write_Fq2_element(uint8_t *out, alt_bn128_Fq2 x) {+ // surprising "big-endian" encoding+ write_Fq_element(out+32, x.c0);+ write_Fq_element(out, x.c1);+ return;+ }++ // writes a point of G1+ // produces 64 bytes+ void write_G1_point(uint8_t *out, alt_bn128_G1 a) {+ // point at infinity is represented as (0,0)+ // so treat it as a special case+ if (a.is_zero()) {+ write_Fq_element(out, alt_bn128_Fq::zero());+ write_Fq_element(out+32, alt_bn128_Fq::zero());+ return;+ }+ a.to_affine_coordinates();+ write_Fq_element(out, a.X);+ write_Fq_element(out+32, a.Y);+ return;+ }++ // writes a point of G2+ // produces 128 bytes+ void write_G2_point(uint8_t *out, alt_bn128_G2 a) {+ // point at infinity is represented as (0,0)+ // so treat it as a special case+ if (a.is_zero()) {+ write_Fq2_element(out, alt_bn128_Fq2::zero());+ write_Fq2_element(out+64, alt_bn128_Fq2::zero());+ return;+ }+ a.to_affine_coordinates();+ write_Fq2_element(out, a.X);+ write_Fq2_element(out+64, a.Y);+ return;+ }++ // writes a bool+ // produces 32 bytes+ void write_bool(uint8_t *out, bool p) {+ out[31] = (int)(p);+ for (int i = 2; i <= 32; i++) {+ out[32-i] = 0;+ }+ }+}++extern "C" {+ using namespace ethjet_ff;++ int+ ethjet_ecadd (uint8_t *in, size_t in_size,+ uint8_t *out, size_t out_size) {++ if (in_size != 128) {+ return 0;+ }+ if (out_size != 64) {+ return 0;+ }++ init();++ try {+ alt_bn128_G1 a = read_G1_point(in);+ alt_bn128_G1 b = read_G1_point(in+64);+ alt_bn128_G1 sum = (a + b);++ write_G1_point(out, sum);+ }+ catch (int e) {+ return 0;+ }++ return 1;+ }++ int+ ethjet_ecmul (uint8_t *in, size_t in_size,+ uint8_t *out, size_t out_size) {++ if (in_size != 96) {+ return 0;+ }+ if (out_size != 64) {+ return 0;+ }++ init();++ try {+ alt_bn128_G1 a = read_G1_point(in);+ alt_bn128_Fr n = read_Fr_element(in+64);+ alt_bn128_G1 na = n * a;++ write_G1_point(out, na);+ }+ catch (int e) {+ return 0;+ }++ return 1;+ }++ int+ ethjet_ecpairing (uint8_t *in, size_t in_size,+ uint8_t *out, size_t out_size) {++ if (in_size % 192 != 0)+ return 0;++ if (out_size != 32)+ return 0;++ init();+ int pairs = in_size / 192;++ try {+ alt_bn128_Fq12 x = libff::alt_bn128_Fq12::one();+ for (int i = 0; i < pairs; i++) {+ alt_bn128_G1 a = read_G1_point(in + i*192);+ alt_bn128_G2 b = read_G2_point(in + i*192 + 64);+ if (a.is_zero() || b.is_zero())+ continue;+ x = x * alt_bn128_miller_loop(alt_bn128_precompute_G1(a), alt_bn128_precompute_G2(b));+ }+ bool result;+ if (pairs == 0)+ result = true;+ else+ result = (alt_bn128_final_exponentiation(x) == alt_bn128_GT::one());+ write_bool(out, result);+ }+ catch (int e) {+ return 0;+ }++ return 1;+ }+}
@@ -0,0 +1,18 @@+#ifndef ETHJET_FF_H+#define ETHJET_FF_H++#include <stdint.h>++int+ethjet_ecadd (uint8_t *in, size_t in_size,+ uint8_t *out, size_t out_size);++int+ethjet_ecmul (uint8_t *in, size_t in_size,+ uint8_t *out, size_t out_size);++int+ethjet_ecpairing (uint8_t *in, size_t in_size,+ uint8_t *out, size_t out_size);++#endif
@@ -1,5 +1,7 @@ #include "ethjet.h"+#include "ethjet-ff.h" #include "tinykeccak.h"+#include "blake2.h" #include <secp256k1_recovery.h> @@ -68,6 +70,11 @@ input64 = in + 64; recid = in[63] - 27; + /* higher bytes of V should be zero */+ static const char z31 [31];+ if (memcmp (z31, in + 32, 31))+ return 0;+ if (recid < 0 || recid > 3) return 0; @@ -90,6 +97,21 @@ return 1; } +int ethjet_blake2(uint8_t *in, size_t in_size,+ uint8_t *out, size_t out_size) {+ uint32_t rounds = in[0] << 24 | in[1] << 16 | in[2] << 8 | in[3];+ unsigned char f = in[212];+ uint64_t *h = (uint64_t *)&in[4];+ uint64_t *m = (uint64_t *)&in[68];+ uint64_t *t = (uint64_t *)&in[196];++ blake2b_compress(h, m, t, f, rounds);++ memcpy(out, h, out_size);++ return 1;+}+ int ethjet (struct ethjet_context *ctx, enum ethjet_operation op,@@ -103,6 +125,18 @@ case ETHJET_EXAMPLE: return ethjet_example (ctx, in, in_size, out, out_size);++ case ETHJET_ECADD:+ return ethjet_ecadd (in, in_size, out, out_size);++ case ETHJET_ECMUL:+ return ethjet_ecmul (in, in_size, out, out_size);++ case ETHJET_ECPAIRING:+ return ethjet_ecpairing (in, in_size, out, out_size);++ case ETHJET_BLAKE2:+ return ethjet_blake2 (in, in_size, out, out_size); default: return 0;
@@ -13,6 +13,10 @@ enum ethjet_operation { ETHJET_ECRECOVER = 1,+ ETHJET_ECADD = 6,+ ETHJET_ECMUL = 7,+ ETHJET_ECPAIRING = 8,+ ETHJET_BLAKE2 = 9, ETHJET_EXAMPLE = 0xdeadbeef, };
@@ -1,132 +1,238 @@ -- Main file of the hevm CLI program -{-# Language BangPatterns #-} {-# Language CPP #-}+{-# Language DataKinds #-}+{-# Language FlexibleInstances #-} {-# Language DeriveGeneric #-}-{-# Language GeneralizedNewtypeDeriving #-}+{-# Language GADTs #-} {-# Language LambdaCase #-} {-# Language NumDecimals #-} {-# Language OverloadedStrings #-}-{-# Language TemplateHaskell #-}+{-# Language TypeOperators #-} -import EVM.Concrete (w256)+module Main where -import qualified EVM as EVM+import EVM (StorageModel(..))+import qualified EVM+import EVM.Concrete (createAddress, w256)+import EVM.Symbolic (forceLitBytes, litBytes, litAddr, w256lit, sw256, SymWord(..), Buffer(..), len) import qualified EVM.FeeSchedule as FeeSchedule import qualified EVM.Fetch import qualified EVM.Flatten import qualified EVM.Stepper-import qualified EVM.TTY as EVM.TTY-import qualified EVM.Emacs as EVM.Emacs+import qualified EVM.TTY+import qualified EVM.Emacs #if MIN_VERSION_aeson(1, 0, 0) import qualified EVM.VMTest as VMTest #endif +import EVM.SymExec import EVM.Debug-import EVM.Exec+import EVM.ABI import EVM.Solidity import EVM.Types hiding (word) import EVM.UnitTest (UnitTestOptions, coverageReport, coverageForUnitTestContract) import EVM.UnitTest (runUnitTestContract) import EVM.UnitTest (getParametersFromEnvironmentVariables, testNumber)-import EVM.Dapp (findUnitTests, dappInfo)--import qualified EVM.UnitTest as EVM.UnitTest+import EVM.Dapp (findUnitTests, dappInfo, DappInfo)+import EVM.Format (showTraceTree)+import EVM.RLP (rlpdecode)+import qualified EVM.Patricia as Patricia+import Data.Map (Map) -import qualified Paths_hevm as Paths+import qualified EVM.Facts as Facts+import qualified EVM.Facts.Git as Git+import qualified EVM.UnitTest import Control.Concurrent.Async (async, waitCatch)-import Control.Exception (evaluate)-import Control.Lens-import Control.Monad (void, when, forM_)-import Control.Monad.State.Strict (execState)+import Control.Lens hiding (pre)+import Control.Monad (void, when, forM_, unless)+import Control.Monad.State.Strict (execStateT) import Data.ByteString (ByteString) import Data.List (intercalate, isSuffixOf) import Data.Text (Text, unpack, pack)-import Data.Maybe (fromMaybe)+import Data.Text.Encoding (encodeUtf8)+import Data.Text.IO (hPutStr)+import Data.Maybe (fromMaybe, fromJust)+import Data.Version (showVersion)+import Data.SBV hiding (Word, solver, verbose, name)+import Data.SBV.Control hiding (Version, timeout, create)+import System.IO (hFlush, hPrint, stdout, stderr) import System.Directory (withCurrentDirectory, listDirectory)-import System.Exit (die, exitFailure)-import System.IO (hFlush, stdout)+import System.Exit (die, exitFailure, exitWith, ExitCode(..))+import System.Environment (setEnv) import System.Process (callProcess)-import System.Timeout (timeout)+import qualified Data.Aeson as JSON+import qualified Data.Aeson.Types as JSON+import Data.Aeson (FromJSON (..), (.:))+import Data.Aeson.Lens hiding (values)+import qualified Data.Vector as V+import qualified Data.ByteString.Lazy as Lazy import qualified Data.ByteString as ByteString-import qualified Data.ByteString.Base16 as BS16 import qualified Data.ByteString.Char8 as Char8 import qualified Data.ByteString.Lazy as LazyByteString import qualified Data.Map as Map-import qualified Options.Generic as Options--import qualified EVM.Facts as Facts-import qualified EVM.Facts.Git as Git+import qualified Data.Sequence as Seq+import qualified System.Timeout as Timeout +import qualified Paths_hevm as Paths import qualified Text.Regex.TDFA as Regex-import qualified Data.Sequence as Seq +import Options.Generic as Options+ -- This record defines the program's command-line options -- automatically via the `optparse-generic` package.-data Command- = Exec- { code :: ByteString- , calldata :: Maybe ByteString- , address :: Maybe Addr- , caller :: Maybe Addr- , origin :: Maybe Addr- , coinbase :: Maybe Addr- , value :: Maybe W256- , gas :: Maybe W256- , number :: Maybe W256- , timestamp :: Maybe W256- , gaslimit :: Maybe W256- , gasprice :: Maybe W256- , difficulty :: Maybe W256- , debug :: Bool- , state :: Maybe String+data Command w+ = Symbolic -- Symbolically explore an abstract program, or specialized with specified env & calldata+ -- vm opts+ { code :: w ::: Maybe ByteString <?> "Program bytecode"+ , calldata :: w ::: Maybe ByteString <?> "Tx: calldata"+ , address :: w ::: Maybe Addr <?> "Tx: address"+ , caller :: w ::: Maybe Addr <?> "Tx: caller"+ , origin :: w ::: Maybe Addr <?> "Tx: origin"+ , coinbase :: w ::: Maybe Addr <?> "Block: coinbase"+ , value :: w ::: Maybe W256 <?> "Tx: Eth amount"+ , nonce :: w ::: Maybe W256 <?> "Nonce of origin"+ , gas :: w ::: Maybe W256 <?> "Tx: gas amount"+ , number :: w ::: Maybe W256 <?> "Block: number"+ , timestamp :: w ::: Maybe W256 <?> "Block: timestamp"+ , gaslimit :: w ::: Maybe W256 <?> "Tx: gas limit"+ , gasprice :: w ::: Maybe W256 <?> "Tx: gas price"+ , create :: w ::: Bool <?> "Tx: creation"+ , maxcodesize :: w ::: Maybe W256 <?> "Block: max code size"+ , difficulty :: w ::: Maybe W256 <?> "Block: difficulty"+ , chainid :: w ::: Maybe W256 <?> "Env: chainId"+ -- remote state opts+ , rpc :: w ::: Maybe URL <?> "Fetch state from a remote node"+ , block :: w ::: Maybe W256 <?> "Block state is be fetched from"++ -- symbolic execution opts+ , jsonFile :: w ::: Maybe String <?> "Filename or path to dapp build output (default: out/*.solc.json)"+ , dappRoot :: w ::: Maybe String <?> "Path to dapp project root directory (default: . )"+ , storageModel :: w ::: Maybe StorageModel <?> "Select storage model: ConcreteS, SymbolicS (default) or InitialS"+ , sig :: w ::: Maybe Text <?> "Signature of types to decode / encode"+ , arg :: w ::: [String] <?> "Values to encode"+ , debug :: w ::: Bool <?> "Run interactively"+ , getModels :: w ::: Bool <?> "Print example testcase for each execution path"+ , smttimeout :: w ::: Maybe Integer <?> "Timeout given to SMT solver in milliseconds (default: 20000)"+ , maxIterations :: w ::: Maybe Integer <?> "Number of times we may revisit a particular branching point"+ , solver :: w ::: Maybe Text <?> "Used SMT solver: z3 (default) or cvc4" }- | DappTest- { jsonFile :: Maybe String- , dappRoot :: Maybe String- , debug :: Bool- , rpc :: Maybe URL- , verbose :: Bool- , coverage :: Bool- , state :: Maybe String- , match :: Maybe String+ | Equivalence -- prove equivalence between two programs+ { codeA :: w ::: ByteString <?> "Bytecode of the first program"+ , codeB :: w ::: ByteString <?> "Bytecode of the second program"+ , sig :: w ::: Maybe Text <?> "Signature of types to decode / encode"+ , smttimeout :: w ::: Maybe Integer <?> "Timeout given to SMT solver in milliseconds (default: 20000)"+ , maxIterations :: w ::: Maybe Integer <?> "Number of times we may revisit a particular branching point"+ , solver :: w ::: Maybe Text <?> "Used SMT solver: z3 (default) or cvc4" }- | Interactive- { jsonFile :: Maybe String- , dappRoot :: Maybe String- , rpc :: Maybe URL- , state :: Maybe String+ | Exec -- Execute a given program with specified env & calldata+ { code :: w ::: Maybe ByteString <?> "Program bytecode"+ , calldata :: w ::: Maybe ByteString <?> "Tx: calldata"+ , address :: w ::: Maybe Addr <?> "Tx: address"+ , caller :: w ::: Maybe Addr <?> "Tx: caller"+ , origin :: w ::: Maybe Addr <?> "Tx: origin"+ , coinbase :: w ::: Maybe Addr <?> "Block: coinbase"+ , value :: w ::: Maybe W256 <?> "Tx: Eth amount"+ , nonce :: w ::: Maybe W256 <?> "Nonce of origin"+ , gas :: w ::: Maybe W256 <?> "Tx: gas amount"+ , number :: w ::: Maybe W256 <?> "Block: number"+ , timestamp :: w ::: Maybe W256 <?> "Block: timestamp"+ , gaslimit :: w ::: Maybe W256 <?> "Tx: gas limit"+ , gasprice :: w ::: Maybe W256 <?> "Tx: gas price"+ , create :: w ::: Bool <?> "Tx: creation"+ , maxcodesize :: w ::: Maybe W256 <?> "Block: max code size"+ , difficulty :: w ::: Maybe W256 <?> "Block: difficulty"+ , chainid :: w ::: Maybe W256 <?> "Env: chainId"+ , debug :: w ::: Bool <?> "Run interactively"+ , trace :: w ::: Bool <?> "Dump trace"+ , state :: w ::: Maybe String <?> "Path to state repository"+ , rpc :: w ::: Maybe URL <?> "Fetch state from a remote node"+ , block :: w ::: Maybe W256 <?> "Block state is be fetched from"+ , jsonFile :: w ::: Maybe String <?> "Filename or path to dapp build output (default: out/*.solc.json)"+ , dappRoot :: w ::: Maybe String <?> "Path to dapp project root directory (default: . )" }- | VmTest- { file :: String- , test :: [String]- , debug :: Bool+ | DappTest -- Run DSTest unit tests+ { jsonFile :: w ::: Maybe String <?> "Filename or path to dapp build output (default: out/*.solc.json)"+ , dappRoot :: w ::: Maybe String <?> "Path to dapp project root directory (default: . )"+ , debug :: w ::: Bool <?> "Run interactively"+ , fuzzRuns :: w ::: Maybe Int <?> "Number of times to run fuzz tests"+ , replay :: w ::: Maybe (Text, ByteString) <?> "Custom fuzz case to run/debug"+ , rpc :: w ::: Maybe URL <?> "Fetch state from a remote node"+ , verbose :: w ::: Maybe Int <?> "Append call trace: {1} failures {2} all"+ , coverage :: w ::: Bool <?> "Coverage analysis"+ , state :: w ::: Maybe String <?> "Path to state repository"+ , match :: w ::: Maybe String <?> "Test case filter - only run methods matching regex" }- | VmTestReport- { tests :: String+ | Interactive -- Browse & run unit tests interactively+ { jsonFile :: w ::: Maybe String <?> "Filename or path to dapp build output (default: out/*.solc.json)"+ , dappRoot :: w ::: Maybe String <?> "Path to dapp project root directory (default: . )"+ , rpc :: w ::: Maybe URL <?> "Fetch state from a remote node"+ , state :: w ::: Maybe String <?> "Path to state repository"+ , replay :: w ::: Maybe (Text, ByteString) <?> "Custom fuzz case to run/debug" }- | Flatten- { sourceFile :: String- , jsonFile :: Maybe String- , dappRoot :: Maybe String+ | BcTest -- Run an Ethereum Blockhain/GeneralState test+ { file :: w ::: String <?> "Path to .json test file"+ , test :: w ::: [String] <?> "Test case filter - only run specified test method(s)"+ , debug :: w ::: Bool <?> "Run interactively"+ , diff :: w ::: Bool <?> "Print expected vs. actual state on failure"+ , timeout :: w ::: Maybe Int <?> "Execution timeout (default: 10 sec.)"+ }+ | Compliance -- Run Ethereum Blockhain compliance report+ { tests :: w ::: String <?> "Path to Ethereum Tests directory"+ , group :: w ::: Maybe String <?> "Report group to run: VM or Blockchain (default: Blockchain)"+ , match :: w ::: Maybe String <?> "Test case filter - only run methods matching regex"+ , skip :: w ::: Maybe String <?> "Test case filter - skip tests containing string"+ , html :: w ::: Bool <?> "Output html report"+ , timeout :: w ::: Maybe Int <?> "Execution timeout (default: 10 sec.)"+ }+ | Flatten -- Concat all dependencies for a given source file+ { sourceFile :: w ::: String <?> "Path to solidity source file e.g. src/contract.sol"+ , jsonFile :: w ::: Maybe String <?> "Filename or path to dapp build output (default: out/*.solc.json)"+ , dappRoot :: w ::: Maybe String <?> "Path to dapp project root directory (default: . )" } | Emacs- deriving (Show, Options.Generic, Eq)+ | Version+ | Rlp -- RLP decode a string and print the result+ { decode :: w ::: ByteString <?> "RLP encoded hexstring"+ }+ | Abiencode+ { abi :: w ::: Maybe String <?> "Signature of types to decode / encode"+ , arg :: w ::: [String] <?> "Values to encode"+ }+ | MerkleTest -- Insert a set of key values and check against the given root+ { file :: w ::: String <?> "Path to .json test file"+ }+ | StripMetadata -- Remove metadata from contract bytecode+ { code :: w ::: Maybe ByteString <?> "Program bytecode"+ } + deriving (Options.Generic)+ type URL = Text -instance Options.ParseRecord Command where++-- For some reason haskell can't derive a+-- parseField instance for (Text, ByteString)+instance Options.ParseField (Text, ByteString)++instance Options.ParseRecord (Command Options.Wrapped) where parseRecord = Options.parseRecordWithModifiers Options.lispCaseModifiers -optsMode :: Command -> Mode+optsMode :: Command Options.Unwrapped -> Mode optsMode x = if debug x then Debug else Run -unitTestOptions :: Command -> IO UnitTestOptions-unitTestOptions cmd = do+unitTestOptions :: Command Options.Unwrapped -> String -> IO UnitTestOptions+unitTestOptions cmd testFile = do+ let root = fromMaybe "." (dappRoot cmd)+ srcInfo <- readSolc testFile >>= \case+ Nothing -> error "Could not read .sol.json file"+ Just (contractMap, sourceCache) ->+ pure $ dappInfo root contractMap sourceCache+ vmModifier <- case state cmd of Nothing ->@@ -137,45 +243,64 @@ params <- getParametersFromEnvironmentVariables + let+ testn = testNumber params+ block' = if 0 == testn+ then EVM.Fetch.Latest+ else EVM.Fetch.BlockNumber testn+ pure EVM.UnitTest.UnitTestOptions { EVM.UnitTest.oracle = case rpc cmd of- Just url -> EVM.Fetch.http (EVM.Fetch.BlockNumber (testNumber params)) url+ Just url -> EVM.Fetch.http block' url Nothing -> EVM.Fetch.zero , EVM.UnitTest.verbose = verbose cmd , EVM.UnitTest.match = pack $ fromMaybe "^test" (match cmd)+ , EVM.UnitTest.fuzzRuns = fromMaybe 100 (fuzzRuns cmd)+ , EVM.UnitTest.replay = do+ arg' <- replay cmd+ return (fst arg', LazyByteString.fromStrict (hexByteString "--replay" $ strip0x $ snd arg')) , EVM.UnitTest.vmModifier = vmModifier , EVM.UnitTest.testParams = params+ , EVM.UnitTest.dapp = srcInfo } main :: IO () main = do- cmd <- Options.getRecord "hevm -- Ethereum evaluator"+ cmd <- Options.unwrapRecord "hevm -- Ethereum evaluator" let root = fromMaybe "." (dappRoot cmd) case cmd of+ Version {} -> putStrLn (showVersion Paths.version)+ Symbolic {} -> assert cmd+ Equivalence {} -> equivalence cmd Exec {} -> launchExec cmd- VmTest {} ->- launchVMTest cmd+ Abiencode {} ->+ print . ByteStringS $ abiencode (abi cmd) (arg cmd)+ BcTest {} ->+ launchTest cmd DappTest {} -> withCurrentDirectory root $ do testFile <- findJsonFile (jsonFile cmd)- testOpts <- unitTestOptions cmd- case coverage cmd of- False ->+ testOpts <- unitTestOptions cmd testFile+ case (coverage cmd, optsMode cmd) of+ (False, Run) -> dappTest testOpts (optsMode cmd) testFile- True ->+ (False, Debug) ->+ EVM.TTY.main testOpts root testFile+ (True, _) -> dappCoverage testOpts (optsMode cmd) testFile Interactive {} -> withCurrentDirectory root $ do testFile <- findJsonFile (jsonFile cmd)- testOpts <- unitTestOptions cmd+ testOpts <- unitTestOptions cmd testFile EVM.TTY.main testOpts root testFile- VmTestReport {} ->- withCurrentDirectory (tests cmd) $ do- dataDir <- Paths.getDataDir- callProcess "bash" [dataDir ++ "/run-consensus-tests", "."]+ Compliance {} ->+ case (group cmd) of+ Just "Blockchain" -> launchScript "/run-blockchain-tests" cmd+ Just "VM" -> launchScript "/run-consensus-tests" cmd+ _ -> launchScript "/run-blockchain-tests" cmd Flatten {} -> withCurrentDirectory root $ do theJson <- findJsonFile (jsonFile cmd)@@ -188,7 +313,27 @@ error ("Failed to read Solidity JSON for `" ++ theJson ++ "'") Emacs -> EVM.Emacs.main+ Rlp {} ->+ case rlpdecode $ hexByteString "--decode" $ strip0x $ decode cmd of+ Nothing -> error "Malformed RLP string"+ Just c -> print c+ MerkleTest {} -> merkleTest cmd+ StripMetadata {} -> print .+ ByteStringS . stripBytecodeMetadata . hexByteString "bytecode" . strip0x $ fromJust $ code cmd +launchScript :: String -> Command Options.Unwrapped -> IO ()+launchScript script cmd =+ withCurrentDirectory (tests cmd) $ do+ dataDir <- Paths.getDataDir+ callProcess "bash"+ [ dataDir ++ script+ , "."+ , show (html cmd)+ , fromMaybe "" (match cmd)+ , fromMaybe "" (skip cmd)+ , show $ fromMaybe 10 (timeout cmd)+ ]+ findJsonFile :: Maybe String -> IO String findJsonFile (Just s) = pure s findJsonFile Nothing = do@@ -210,12 +355,12 @@ ] dappTest :: UnitTestOptions -> Mode -> String -> IO ()-dappTest opts _ solcFile = do+dappTest opts _ solcFile = readSolc solcFile >>= \case Just (contractMap, cache) -> do let matcher = regexMatches (EVM.UnitTest.match opts)- let unitTests = (findUnitTests matcher) (Map.elems contractMap)+ unitTests = (findUnitTests matcher) (Map.elems contractMap) results <- mapM (runUnitTestContract opts contractMap cache) unitTests when (any (== False) results) exitFailure Nothing ->@@ -232,8 +377,154 @@ in Regex.matchTest regex . Seq.fromList . unpack +equivalence :: Command Options.Unwrapped -> IO ()+equivalence cmd =+ do let bytecodeA = hexByteString "--code" . strip0x $ codeA cmd+ bytecodeB = hexByteString "--code" . strip0x $ codeB cmd+ maybeSignature <- case sig cmd of+ Nothing -> return Nothing+ Just sig' -> do method' <- functionAbi sig'+ return $ Just (view methodSignature method', snd <$> view methodInputs method')++ void . runSMTWithTimeOut (solver cmd) (smttimeout cmd) . query $+ equivalenceCheck bytecodeA bytecodeB (maxIterations cmd) maybeSignature >>= \case+ Right vm -> do io $ putStrLn "Not equal!"+ io $ putStrLn "Counterexample:"+ showCounterexample vm maybeSignature+ io $ exitFailure+ Left (postAs, postBs) -> io $ do+ putStrLn $ "Explored: " <> show (length postAs)+ <> " execution paths of A and: "+ <> show (length postBs) <> " paths of B."+ putStrLn $ "No discrepancies found."+++-- cvc4 sets timeout via a commandline option instead of smtlib `(set-option)`+runSMTWithTimeOut :: Maybe Text -> Maybe Integer -> Symbolic a -> IO a+runSMTWithTimeOut solver maybeTimeout sym+ | solver == Just "cvc4" = do+ setEnv "SBV_CVC4_OPTIONS" ("--lang=smt --incremental --interactive --no-interactive-prompt --model-witness-value --tlimit-per=" <> show timeout)+ a <- runSMTWith cvc4 sym+ setEnv "SBV_CVC4_OPTIONS" ""+ return a+ | solver == Just "z3" = runwithz3+ | solver == Nothing = runwithz3+ | otherwise = error "Unknown solver. Currently supported solvers; z3, cvc4"+ where timeout = fromMaybe 20000 maybeTimeout+ runwithz3 = runSMTWith z3 $ (setTimeOut timeout) >> sym+++checkForVMErrors :: [EVM.VM] -> [String]+checkForVMErrors [] = []+checkForVMErrors (vm:vms) =+ case view EVM.result vm of+ Just (EVM.VMFailure EVM.UnexpectedSymbolicArg) ->+ ("Unexpected symbolic argument at opcode: "+ <> maybe "??" show (EVM.vmOp vm)+ <> ". Not supported (yet!)"+ ) : checkForVMErrors vms+ _ ->+ checkForVMErrors vms++emptyDapp :: DappInfo+emptyDapp = dappInfo "" mempty (SourceCache mempty mempty mempty mempty)++getSrcInfo :: Command Options.Unwrapped -> IO DappInfo+getSrcInfo cmd =+ let root = fromMaybe "." (dappRoot cmd)+ in case (jsonFile cmd) of+ Nothing ->+ pure $ emptyDapp+ Just json -> readSolc json >>= \case+ Nothing ->+ pure $ emptyDapp+ Just (contractMap, sourceCache) ->+ pure $ dappInfo root contractMap sourceCache++-- Although it is tempting to fully abstract calldata and give any hints about+-- the nature of the signature doing so results in significant time spent in+-- consulting z3 about rather trivial matters. But with cvc4 it is quite+-- pleasant!++-- If function signatures are known, they should always be given for best results.+assert :: Command Options.Unwrapped -> IO ()+assert cmd = do+ let block' = maybe EVM.Fetch.Latest EVM.Fetch.BlockNumber (block cmd)+ rpcinfo = (,) block' <$> rpc cmd+ model = fromMaybe (if create cmd then InitialS else SymbolicS) (storageModel cmd)+ srcInfo <- getSrcInfo cmd+ maybesig <- case sig cmd of+ Nothing ->+ return Nothing+ Just sig' -> do+ method' <- functionAbi sig'+ let typ = snd <$> view methodInputs method'+ name = view methodSignature method'+ return $ Just (name,typ)+ if debug cmd then+ runSMTWithTimeOut (solver cmd) (smttimeout cmd) $ query $ do+ preState <- symvmFromCommand cmd+ smtState <- queryState+ io $ void $ EVM.TTY.runFromVM+ (maxIterations cmd)+ srcInfo+ (EVM.Fetch.oracle smtState rpcinfo model)+ preState++ else+ runSMTWithTimeOut (solver cmd) (smttimeout cmd) $ query $ do+ preState <- symvmFromCommand cmd+ verify preState (maxIterations cmd) rpcinfo (Just checkAssertions) >>= \case+ Right _ -> do+ io $ putStrLn "Assertion violation found."+ showCounterexample preState maybesig+ io $ exitWith (ExitFailure 1)+ Left (pre, posts) -> do+ io $ putStrLn $ "Explored: " <> show (length posts)+ <> " branches without assertion violations"+ let vmErrs = checkForVMErrors posts+ unless (null vmErrs) $ io $ do+ putStrLn $+ "However, "+ <> show (length vmErrs)+ <> " branch(es) errored while exploring:"+ print vmErrs+ -- When `--get-models` is passed, we print example vm info for each path+ when (getModels cmd) $+ forM_ (zip [1..] posts) $ \(i, postVM) -> do+ resetAssertions+ constrain (sAnd (view EVM.pathConditions postVM))+ io $ putStrLn $+ "-- Branch (" <> show i <> "/" <> show (length posts) <> ") --"+ checkSat >>= \case+ Unk -> io $ do putStrLn "Timed out"+ print $ view EVM.result postVM+ Unsat -> io $ do putStrLn "Inconsistent path conditions: dead path"+ print $ view EVM.result postVM+ Sat -> do+ showCounterexample pre maybesig+ case view EVM.result postVM of+ Nothing ->+ error "internal error; no EVM result"+ Just (EVM.VMFailure (EVM.Revert "")) -> io . putStrLn $+ "Reverted"+ Just (EVM.VMFailure (EVM.Revert msg)) -> io . putStrLn $+ "Reverted: " <> show (ByteStringS msg)+ Just (EVM.VMFailure err) -> io . putStrLn $+ "Failed: " <> show err+ Just (EVM.VMSuccess (ConcreteBuffer msg)) ->+ if ByteString.null msg+ then io $ putStrLn+ "Stopped"+ else io $ putStrLn $+ "Returned: " <> show (ByteStringS msg)+ Just (EVM.VMSuccess (SymbolicBuffer msg)) -> do+ out <- mapM (getValue.fromSized) msg+ io . putStrLn $+ "Returned: " <> show (ByteStringS (ByteString.pack out))+ dappCoverage :: UnitTestOptions -> Mode -> String -> IO ()-dappCoverage opts _ solcFile = do+dappCoverage opts _ solcFile = readSolc solcFile >>= \case Just (contractMap, cache) -> do@@ -262,9 +553,11 @@ Nothing -> error ("Failed to read Solidity JSON for `" ++ solcFile ++ "'") -launchExec :: Command -> IO ()+launchExec :: Command Options.Unwrapped -> IO () launchExec cmd = do- let vm = vmFromCommand cmd+ dapp <- getSrcInfo cmd++ vm <- vmFromCommand cmd vm1 <- case state cmd of Nothing -> pure vm Just path ->@@ -274,57 +567,248 @@ Facts.apply vm <$> Git.loadFacts (Git.RepoAt path) case optsMode cmd of- Run ->- let vm' = execState exec vm1- in case view EVM.result vm' of+ Run -> do+ vm' <- execStateT (interpret fetcher Nothing . void $ EVM.Stepper.execFully) vm1+ when (trace cmd) $ hPutStr stderr (showTraceTree dapp vm')+ case view EVM.result vm' of Nothing -> error "internal error; no EVM result"- Just (EVM.VMFailure e) -> do- die (show e)- Just (EVM.VMSuccess x) -> do- let hex = BS16.encode x- if ByteString.null hex then pure ()- else do- ByteString.putStr hex- putStrLn ""+ Just (EVM.VMFailure (EVM.Revert msg)) -> do+ print $ ByteStringS msg+ exitWith (ExitFailure 2)+ Just (EVM.VMFailure err) -> do+ print err+ exitWith (ExitFailure 2)+ Just (EVM.VMSuccess buf) -> do+ let msg = case buf of+ SymbolicBuffer msg' -> forceLitBytes msg'+ ConcreteBuffer msg' -> msg'+ print $ ByteStringS msg case state cmd of Nothing -> pure () Just path -> Git.saveFacts (Git.RepoAt path) (Facts.vmFacts vm')- Debug ->- void (EVM.TTY.runFromVM vm1)+ Debug -> void $ EVM.TTY.runFromVM Nothing dapp fetcher vm1+ where fetcher = maybe EVM.Fetch.zero (EVM.Fetch.http block') (rpc cmd)+ block' = maybe EVM.Fetch.Latest EVM.Fetch.BlockNumber (block cmd) -vmFromCommand :: Command -> EVM.VM-vmFromCommand cmd =- vm1 & EVM.env . EVM.contracts . ix address' . EVM.balance +~ (w256 value')+data Testcase = Testcase {+ _entries :: [(Text, Maybe Text)],+ _root :: Text+} deriving Show++parseTups :: JSON.Value -> JSON.Parser [(Text, Maybe Text)]+parseTups (JSON.Array arr) = do+ tupList <- mapM parseJSON (V.toList arr)+ mapM (\[k, v] -> do+ rhs <- parseJSON v+ lhs <- parseJSON k+ return (lhs, rhs))+ tupList+parseTups invalid = JSON.typeMismatch "Malformed array" invalid+++parseTrieTest :: JSON.Object -> JSON.Parser Testcase+parseTrieTest p = do+ kvlist <- p .: "in"+ entries <- parseTups kvlist+ root <- p .: "root"+ return $ Testcase entries root++instance FromJSON Testcase where+ parseJSON (JSON.Object p) = parseTrieTest p+ parseJSON invalid = JSON.typeMismatch "Merkle test case" invalid++parseTrieTests :: Lazy.ByteString -> Either String (Map String Testcase)+parseTrieTests = JSON.eitherDecode'++merkleTest :: Command Options.Unwrapped -> IO ()+merkleTest cmd = do+ parsed <- parseTrieTests <$> LazyByteString.readFile (file cmd)+ case parsed of+ Left err -> print err+ Right testcases -> mapM_ runMerkleTest testcases++runMerkleTest :: Testcase -> IO ()+runMerkleTest (Testcase entries root) =+ case Patricia.calcRoot entries' of+ Nothing ->+ error "Test case failed"+ Just n ->+ case n == strip0x (hexText root) of+ True ->+ putStrLn "Test case success"+ False ->+ error ("Test case failure; expected " <> show root+ <> " but got " <> show (ByteStringS n))+ where entries' = fmap (\(k, v) ->+ (tohexOrText k,+ tohexOrText (fromMaybe mempty v)))+ entries++tohexOrText :: Text -> ByteString+tohexOrText s = case "0x" `Char8.isPrefixOf` encodeUtf8 s of+ True -> hexText s+ False -> encodeUtf8 s++-- | Creates a (concrete) VM from command line options+vmFromCommand :: Command Options.Unwrapped -> IO EVM.VM+vmFromCommand cmd = do+ vm <- case (rpc cmd, address cmd) of+ (Just url, Just addr') -> do+ EVM.Fetch.fetchContractFrom block' url addr' >>= \case+ Nothing -> error $ "contract not found: " <> show address'+ Just contract' -> case code cmd of+ Nothing -> return (vm1 contract')+ -- if both code and url is given,+ -- fetch the contract and overwrite the code+ Just c -> return . vm1 $+ EVM.initialContract (codeType $ hexByteString "--code" $ strip0x c)+ & set EVM.storage (view EVM.storage contract')+ & set EVM.balance (view EVM.balance contract')+ & set EVM.nonce (view EVM.nonce contract')+ & set EVM.external (view EVM.external contract')++ _ -> return . vm1 . EVM.initialContract . codeType $ bytes code ""++ return $ vm & EVM.env . EVM.contracts . ix address' . EVM.balance +~ (w256 value')+ where+ decipher = hexByteString "bytes" . strip0x+ block' = maybe EVM.Fetch.Latest EVM.Fetch.BlockNumber (block cmd)+ value' = word value 0+ caller' = addr caller 0+ origin' = addr origin 0+ calldata' = ConcreteBuffer $ bytes calldata ""+ codeType = if create cmd then EVM.InitCode else EVM.RuntimeCode+ address' = if create cmd+ then createAddress origin' (word nonce 0)+ else addr address 0xacab++ vm1 c = EVM.makeVm $ EVM.VMOpts+ { EVM.vmoptContract = c+ , EVM.vmoptCalldata = (calldata', literal . num $ len calldata')+ , EVM.vmoptValue = w256lit value'+ , EVM.vmoptAddress = address'+ , EVM.vmoptCaller = litAddr caller'+ , EVM.vmoptOrigin = origin'+ , EVM.vmoptGas = word gas 0+ , EVM.vmoptGaslimit = word gas 0+ , EVM.vmoptCoinbase = addr coinbase 0+ , EVM.vmoptNumber = word number 0+ , EVM.vmoptTimestamp = word timestamp 0+ , EVM.vmoptBlockGaslimit = word gaslimit 0+ , EVM.vmoptGasprice = word gasprice 0+ , EVM.vmoptMaxCodeSize = word maxcodesize 0xffffffff+ , EVM.vmoptDifficulty = word difficulty 0+ , EVM.vmoptSchedule = FeeSchedule.istanbul+ , EVM.vmoptChainId = word chainid 1+ , EVM.vmoptCreate = create cmd+ , EVM.vmoptStorageModel = ConcreteS+ }+ word f def = fromMaybe def (f cmd)+ addr f def = fromMaybe def (f cmd)+ bytes f def = maybe def decipher (f cmd)++symvmFromCommand :: Command Options.Unwrapped -> Query EVM.VM+symvmFromCommand cmd = do+ caller' <- maybe (SAddr <$> freshVar_) (return . litAddr) (caller cmd)+ callvalue' <- maybe (sw256 <$> freshVar_) (return . w256lit) (value cmd)+ (calldata', cdlen, pathCond) <- case (calldata cmd, sig cmd) of+ -- fully abstract calldata (up to 1024 bytes)+ (Nothing, Nothing) -> do+ cd <- sbytes256+ len <- freshVar_+ return (SymbolicBuffer cd, len, len .<= 256)+ -- fully concrete calldata+ (Just c, Nothing) ->+ let cd = ConcreteBuffer $ decipher c+ in return (cd, num (len cd), sTrue)+ -- calldata according to given abi with possible specializations from the `arg` list+ (Nothing, Just sig') -> do+ method' <- io $ functionAbi sig'+ let typs = snd <$> view methodInputs method'+ (cd, cdlen) <- symCalldata (view methodSignature method') typs (arg cmd)+ return (SymbolicBuffer cd, cdlen, sTrue)++ _ -> error "incompatible options: calldata and abi"++ store <- case storageModel cmd of+ -- InitialS and SymbolicS can read and write to symbolic locations+ -- ConcreteS cannot (instead values can be fetched from rpc!)+ -- Initial defaults to 0 for uninitialized storage slots,+ -- whereas the values of SymbolicS are unconstrained.+ Just InitialS -> EVM.Symbolic <$> freshArray_ (Just 0)+ Just ConcreteS -> return (EVM.Concrete mempty)+ Just SymbolicS -> EVM.Symbolic <$> freshArray_ Nothing+ Nothing -> EVM.Symbolic <$> freshArray_ (if create cmd then (Just 0) else Nothing)++ vm <- case (rpc cmd, address cmd, code cmd) of+ (Just url, Just addr', _) ->+ io (EVM.Fetch.fetchContractFrom block' url addr') >>= \case+ Nothing ->+ error $ "contract not found."+ Just contract' ->+ return $+ vm1 cdlen calldata' callvalue' caller' (contract'' & set EVM.storage store)+ where+ contract'' = case code cmd of+ Nothing -> contract'+ -- if both code and url is given,+ -- fetch the contract and overwrite the code+ Just c -> EVM.initialContract (codeType $ decipher c)+ & set EVM.storage (view EVM.storage contract')+ & set EVM.origStorage (view EVM.origStorage contract')+ & set EVM.balance (view EVM.balance contract')+ & set EVM.nonce (view EVM.nonce contract')+ & set EVM.external (view EVM.external contract')++ (_, _, Just c) ->+ return $+ vm1 cdlen calldata' callvalue' caller' $+ (EVM.initialContract . codeType $ decipher c) & set EVM.storage store+ (_, _, Nothing) ->+ error $ "must provide at least (rpc + address) or code"++ return $ vm & over EVM.pathConditions (<> [pathCond])+ where- value' = word value 0- address' = addr address 1- vm1 = EVM.makeVm $ EVM.VMOpts- { EVM.vmoptCode = hexByteString "--code" (code cmd)- , EVM.vmoptCalldata = maybe "" (hexByteString "--calldata")- (calldata cmd)- , EVM.vmoptValue = value'- , EVM.vmoptAddress = address'- , EVM.vmoptCaller = addr caller 2- , EVM.vmoptOrigin = addr origin 3- , EVM.vmoptGas = word gas 0- , EVM.vmoptCoinbase = addr coinbase 0- , EVM.vmoptNumber = word number 0- , EVM.vmoptTimestamp = word timestamp 0- , EVM.vmoptGaslimit = word gaslimit 0- , EVM.vmoptGasprice = word gasprice 0- , EVM.vmoptDifficulty = word difficulty 0- , EVM.vmoptSchedule = FeeSchedule.metropolis+ decipher = hexByteString "bytes" . strip0x+ block' = maybe EVM.Fetch.Latest EVM.Fetch.BlockNumber (block cmd)+ origin' = addr origin 0+ codeType = if create cmd then EVM.InitCode else EVM.RuntimeCode+ address' = if create cmd+ then createAddress origin' (word nonce 0)+ else addr address 0xacab+ vm1 cdlen calldata' callvalue' caller' c = EVM.makeVm $ EVM.VMOpts+ { EVM.vmoptContract = c+ , EVM.vmoptCalldata = (calldata', cdlen)+ , EVM.vmoptValue = callvalue'+ , EVM.vmoptAddress = address'+ , EVM.vmoptCaller = caller'+ , EVM.vmoptOrigin = origin'+ , EVM.vmoptGas = word gas 0xffffffffffffffff+ , EVM.vmoptGaslimit = word gas 0xffffffffffffffff+ , EVM.vmoptCoinbase = addr coinbase 0+ , EVM.vmoptNumber = word number 0+ , EVM.vmoptTimestamp = word timestamp 0+ , EVM.vmoptBlockGaslimit = word gaslimit 0+ , EVM.vmoptGasprice = word gasprice 0+ , EVM.vmoptMaxCodeSize = word maxcodesize 0xffffffff+ , EVM.vmoptDifficulty = word difficulty 0+ , EVM.vmoptSchedule = FeeSchedule.istanbul+ , EVM.vmoptChainId = word chainid 1+ , EVM.vmoptCreate = create cmd+ , EVM.vmoptStorageModel = fromMaybe SymbolicS (storageModel cmd) }- word f def = maybe def id (f cmd)- addr f def = maybe def id (f cmd)+ word f def = fromMaybe def (f cmd)+ addr f def = fromMaybe def (f cmd) -launchVMTest :: Command -> IO ()-launchVMTest cmd =+launchTest :: Command Options.Unwrapped -> IO ()+launchTest cmd = do #if MIN_VERSION_aeson(1, 0, 0)- VMTest.parseSuite <$> LazyByteString.readFile (file cmd) >>=- \case+ parsed <- VMTest.parseBCSuite <$> LazyByteString.readFile (file cmd)+ case parsed of+ Left "No cases to check." -> putStrLn "no-cases ok" Left err -> print err Right allTests -> let testFilter =@@ -332,40 +816,55 @@ then id else filter (\(x, _) -> elem x (test cmd)) in- mapM_ (runVMTest (optsMode cmd)) $+ mapM_ (runVMTest (diff cmd) (optsMode cmd) (timeout cmd)) $ testFilter (Map.toList allTests) #else putStrLn "Not supported" #endif #if MIN_VERSION_aeson(1, 0, 0)-runVMTest :: Mode -> (String, VMTest.Case) -> IO Bool-runVMTest mode (name, x) = do- let- vm0 = VMTest.vmForCase x- m = void EVM.Stepper.execFully >> EVM.Stepper.evm EVM.finalize-+runVMTest :: Bool -> Mode -> Maybe Int -> (String, VMTest.Case) -> IO Bool+runVMTest diffmode mode timelimit (name, x) =+ do+ let vm0 = VMTest.vmForCase x putStr (name ++ " ") hFlush stdout result <- do action <- async $ case mode of Run ->- timeout (1e6) . evaluate $ do- execState (VMTest.interpret m) vm0+ Timeout.timeout (1e6 * (fromMaybe 10 timelimit)) $+ execStateT (EVM.Stepper.interpret EVM.Fetch.zero . void $ EVM.Stepper.execFully) vm0 Debug ->- Just <$> EVM.TTY.runFromVM vm0+ Just <$> EVM.TTY.runFromVM Nothing emptyDapp EVM.Fetch.zero vm0 waitCatch action case result of Right (Just vm1) -> do- ok <- VMTest.checkExpectation x vm1+ ok <- VMTest.checkExpectation diffmode x vm1 putStrLn (if ok then "ok" else "") return ok Right Nothing -> do putStrLn "timeout" return False- Left _ -> do- putStrLn "error"+ Left e -> do+ putStrLn $ "error: " ++ if diffmode+ then show e+ else (head . lines . show) e return False #endif++parseAbi :: (AsValue s) => s -> (Text, [AbiType])+parseAbi abijson =+ (signature abijson, snd+ <$> parseMethodInput+ <$> V.toList+ (fromMaybe (error "Malformed function abi") (abijson ^? key "inputs" . _Array)))++abiencode :: (AsValue s) => Maybe s -> [String] -> ByteString+abiencode Nothing _ = error "missing required argument: abi"+abiencode (Just abijson) args =+ let (sig', declarations) = parseAbi abijson+ in if length declarations == length args+ then abiMethod sig' $ AbiTuple . V.fromList $ zipWith makeAbiValue declarations args+ else error $ "wrong number of arguments:" <> show (length args) <> ": " <> show args
@@ -1,7 +1,8 @@+cabal-version: 2.2 name: hevm version:- 0.24+ 0.41.0 synopsis: Ethereum virtual machine evaluator description:@@ -12,20 +13,19 @@ homepage: https://github.com/dapphub/dapptools license:- AGPL-3+ AGPL-3.0-only license-file: COPYING author:- Mikael Brockman+ Mikael Brockman, Martin Lundfall maintainer:- mikael@brockman.se+ mikael@brockman.se, martin.lundfall@gmail.com category: Ethereum build-type: Simple-cabal-version:- >=1.10 data-files:+ run-blockchain-tests run-consensus-tests extra-source-files: CHANGELOG.md@@ -38,6 +38,7 @@ EVM.Dapp, EVM.Dev, EVM.Debug,+ EVM.Demand, EVM.Emacs, EVM.Exec, EVM.Facts,@@ -47,73 +48,88 @@ EVM.Fetch, EVM.FeeSchedule, EVM.Hexdump,+ EVM.Keccak, EVM.Op,+ EVM.Patricia, EVM.Precompiled,- EVM.Keccak,+ EVM.RLP, EVM.Solidity, EVM.Stepper, EVM.StorageLayout,+ EVM.Symbolic,+ EVM.SymExec+ EVM.Transaction, EVM.TTY, EVM.TTYCenteredList, EVM.Types, EVM.UnitTest,- EVM.VMTest,+ EVM.VMTest+ other-modules: Paths_hevm+ autogen-modules:+ Paths_hevm ghc-options: -Wall -Wno-deprecations extra-libraries:- secp256k1+ secp256k1, ff c-sources:- ethjet/ethjet.c, ethjet/tinykeccak.c+ ethjet/tinykeccak.c, ethjet/ethjet.c+ cxx-sources:+ ethjet/ethjet-ff.cc, ethjet/blake2.cc+ cxx-options:+ -std=c++0x install-includes:- ethjet/ethjet.h, ethjet/tinykeccak.h+ ethjet/tinykeccak.h, ethjet/ethjet.h, ethjet/ethjet-ff.h, ethjet/blake2.h build-depends:- QuickCheck >= 2.9,- abstract-par >= 0.3,- aeson >= 1.0,- ansi-wl-pprint >= 0.6,- base >= 4.9 && < 5,- base16-bytestring >= 0.1,- base64-bytestring >= 1.0,- binary >= 0.8,- brick >= 0.18,- bytestring >= 0.10,- cereal >= 0.5,- containers >= 0.5,- cryptonite >= 0.21,- data-dword >= 0.3,- deepseq >= 1.4,- directory >= 1.3,- filepath >= 1.4,- fgl >= 5.0,- ghci-pretty >= 0.0,- haskeline >= 0.7,- time >= 1.8,- transformers >= 0.5,- lens >= 4.15,- lens-aeson >= 1.0,- megaparsec >= 6.0,- memory >= 0.14,- monad-par >= 0.3,- mtl >= 2.2,- multiset >= 0.3,- operational >= 0.2,- optparse-generic >= 1.1,- process >= 1.4,- quickcheck-text >= 0.1,- readline >= 1.0,- restless-git >= 0.7,- rosezipper >= 0.2,- scientific >= 0.3,- s-cargot >= 0.1,- temporary >= 1.2,- text >= 1.2,- text-format >= 0.3,+ QuickCheck >= 2.13.2 && < 2.14,+ containers >= 0.6.0 && < 0.7,+ deepseq >= 1.4.4 && < 1.5,+ time >= 1.8.0 && < 1.9,+ transformers >= 0.5.6 && < 0.6, tree-view == 0.5,- unordered-containers >= 0.2,- vector >= 0.11,- vty >= 5.15,- wreq >= 0.5+ abstract-par >= 0.3.3 && < 0.4,+ aeson >= 1.4.6 && < 1.5,+ bytestring >= 0.10.8 && < 0.11,+ scientific >= 0.3.6 && < 0.4,+ binary >= 0.8.6 && < 0.9,+ text >= 1.2.3 && < 1.3,+ unordered-containers >= 0.2.10 && < 0.3,+ vector >= 0.12.1 && < 0.13,+ ansi-wl-pprint >= 0.6.9 && < 0.7,+ base16-bytestring >= 0.1.1 && < 0.2,+ brick >= 0.47.1 && < 0.48,+ megaparsec >= 7.0.5 && < 7.1,+ mtl >= 2.2.2 && < 2.3,+ directory >= 1.3.3 && < 1.4,+ filepath >= 1.4.2 && < 1.5,+ vty >= 5.25.1 && < 5.26,+ cborg >= 0.2.2 && < 0.3,+ cereal >= 0.5.8 && < 0.6,+ cryptonite >= 0.25 && < 0.26,+ memory >= 0.14.18 && < 0.15,+ data-dword >= 0.3.1 && < 0.4,+ fgl >= 5.7.0 && < 5.8,+ free >= 5.1.3 && < 5.2,+ haskeline >= 0.7.4 && < 0.8,+ process >= 1.6.5 && < 1.7,+ lens >= 4.17.1 && < 4.18,+ lens-aeson >= 1.0.2 && < 1.1,+ monad-par >= 0.3.5 && < 0.4,+ multiset >= 0.3.4 && < 0.4,+ operational >= 0.2.3 && < 0.3,+ optparse-generic >= 1.3.1 && < 1.4,+ quickcheck-text >= 0.1.2 && < 0.2,+ restless-git >= 0.7 && < 0.8,+ rosezipper >= 0.2 && < 0.3,+ s-cargot >= 0.1.4 && < 0.2,+ sbv >= 8.7.5 && < 8.8,+ semver-range >= 0.2.7 && < 0.3,+ temporary >= 1.3 && < 1.4,+ text-format >= 0.3.2 && < 0.4,+ witherable >= 0.3.5 && < 0.4,+ wreq >= 0.5.3 && < 0.6,+ regex-tdfa >= 1.2.3 && < 1.3,+ base >= 4.9 && < 5 hs-source-dirs: src default-language:@@ -140,40 +156,44 @@ hevm-cli.hs ghc-options: -Wall -threaded -with-rtsopts=-N+ other-modules:+ Paths_hevm+ if os(darwin)+ ld-options: -Wl,-keep_dwarf_unwind build-depends:- QuickCheck >= 2.9,- aeson >= 1.0,- ansi-wl-pprint >= 0.6,- async == 2.*,- base >= 4.9 && < 5,- base16-bytestring >= 0.1,- base64-bytestring >= 1.0,- binary >= 0.8,- brick >= 0.18,- bytestring >= 0.10,- containers >= 0.5,- cryptonite >= 0.21,- data-dword >= 0.3,- deepseq >= 1.4,- directory >= 1.3,- filepath >= 1.4,- ghci-pretty >= 0.0,+ QuickCheck,+ aeson,+ ansi-wl-pprint,+ async,+ base,+ base16-bytestring,+ binary,+ brick,+ bytestring,+ containers,+ cryptonite,+ data-dword,+ deepseq,+ directory,+ filepath,+ free, hevm,- lens >= 4.15,- lens-aeson >= 1.0,- memory >= 0.14,- mtl >= 2.2,- optparse-generic >= 1.1,- process >= 1.4,- quickcheck-text >= 0.1,- readline >= 1.0,- regex-tdfa >= 1.2,- temporary >= 1.2,- text >= 1.2,- text-format >= 0.3,- unordered-containers >= 0.2,- vector >= 0.11,- vty >= 5.15+ lens,+ lens-aeson,+ memory,+ mtl,+ optparse-generic,+ operational,+ process,+ quickcheck-text,+ regex-tdfa,+ sbv,+ temporary,+ text,+ text-format,+ unordered-containers,+ vector,+ vty test-suite test default-language:@@ -190,18 +210,20 @@ secp256k1 build-depends: HUnit >= 1.6,- QuickCheck >= 2.9,- base >= 4.9 && < 5,- base16-bytestring >= 0.1,- ghci-pretty >= 0.0.2,+ QuickCheck,+ base,+ base16-bytestring,+ binary,+ containers,+ bytestring,+ free,+ here, hevm,+ lens,+ mtl, tasty >= 1.0, tasty-hunit >= 0.10, tasty-quickcheck >= 0.9,- mtl >= 2.2,- lens >= 4.16,- text >= 1.2,- here >= 1.2,- bytestring >= 0.10,- vector >= 0.12,- binary >= 0.8+ text,+ vector,+ sbv
@@ -0,0 +1,136 @@+#!/usr/bin/env bash+set -e++# Invoke with hevm e.g.+# hevm compliance --tests ~/ethereum-tests --skip modexp --timeout 20 --html++HEVM=${HEVM:-hevm}++if [[ "$#" -lt 1 ]]; then+ echo >&2 "usage: $(basename "$0") <tests-dir>"+ exit 1+fi++tests=$1+html=$2+match=$3+skip=$4+timeout=${5:-10}++_html () {+cat <<.+<!doctype html>+<title>hevm test results</title>+<style>+* { font-family:+"latin modern mono", "fantasque sans mono",+inconsolata, menlo, monospace;+font-size: 22px;+line-height: 26px; }+body { margin: 2rem; }+header { text-align: center; margin: 4rem 0; }+table { border-collapse: collapse; width: 100%; }+tr:nth-child(even) { background: rgba(0, 0, 0, 0.05); }+td:not(:first-child):not(:last-child) { padding: 0 1rem; }+.category { opacity: 0.6; text-align: right }+a { color: darkblue; text-decoration: none; }+h1, h2 { text-align: center; margin-top: 2rem }+.testcase { font-weight: bold }+#failed .testcase { color: rgb(200, 0, 0) }+#balancefailed .testcase { color: rgb(200, 200, 0) }+#passed .testcase { color: rgb(0, 150, 0) }+#skipped .testcase { color: rgb(0, 100, 250) }+#timeout .testcase { color: rgb(0, 0, 250) }+</style>+<header>+<h1>hevm consensus test report</h1>+<p>$(date +%Y-%m-%d)</p>+<p>$(echo "$npass passed, $nbal bad-balance, $nnon bad-nonce, $nstr bad-storage, $nfail failed, $nskip skipped, $ntime timeout")</p>+(Test suite: <span class=GeneralStateTests</span>GeneralStateTests</span> for Istanbul)+</header>+<h2>Failed tests</h2>+<table id=failed>+<tbody>+$(echo $noncefailed)+$(echo $storagefailed)+$(echo $failed)+</table>+<h2>Failed tests (due to balance only)</h2>+<table id=balancefailed>+<tbody>+$(echo $balancefailed)+</table>+<h2>Timeout tests</h2>+<table id=timeout>+<tbody>+$(echo $timeouts)+</table>+<h2>Skipped tests</h2>+<table id=skipped>+<tbody>+$(echo $skipped)+</table>+<h2>Passed tests</h2>+<table id=passed>+<tbody>+$(echo $passed)+</table>+.+}++shopt -s nocasematch+{+ cd "$tests"+ for x in BlockchainTests/GeneralStateTests/*/*; do+ if [[ $x =~ .*$match.* ]] && [[ -n $skip && $x =~ .*$skip.* ]]; then+ for job in $(<$x jq '.|keys[]' -r); do+ echo -n "$job " ; echo "skip"+ done+ elif [[ $x =~ .*$match.* ]]; then+ set +e+ "$HEVM" bc-test --file $x --timeout $timeout 2>&1+ set -e+ fi+ done+} | {+ while read test outcome; do+ echo >&2 "$test $outcome"+ row="<tr><td class=testcase>$test<td>$outcome"+ row+=$'\n'+ case $outcome in+ ok) passed+=$row ;;+ bad-balance) balancefailed+=$row ;;+ bad-nonce) noncefailed+=$row ;;+ bad-storage) storagefailed+=$row ;;+ timeout) timeouts+=$row ;;+ skip) skipped+=$row ;;+ *) failed+=$row ;;+ esac+ done++ sum () { echo -ne "$1" | wc -l | awk '{print $1}'; }++ npass=$(sum "$passed")+ nbal=$(sum "$balancefailed")+ nnon=$(sum "$noncefailed")+ nstr=$(sum "$storagefailed")+ nfail=$(sum "$failed")+ ntime=$(sum "$timeouts")+ nskip=$(sum "$skipped")++ echo >&2 "passed: $npass"+ echo >&2 "bad-balance: $nbal"+ echo >&2 "bad-nonce: $nnon"+ echo >&2 "bad-storage: $nstr"+ echo >&2 "failed: $nfail"+ echo >&2 "timeout: $ntime"+ echo >&2 "skipped: $nskip"++ if [[ $html == "True" ]]; then+ _html+ fi++ nbad=$(($nbal + $nnon + $nstr + $nfail))++ [[ $nbad -gt 0 ]] && exit 1 || exit 0+}
@@ -1,41 +1,32 @@ #!/usr/bin/env bash set -e -if [[ $# != 1 ]]; then+# Invoke with hevm e.g.+# hevm compliance --tests ~/ethereum-tests --group VM --skip quadratic --html++HEVM=${HEVM:-hevm}++if [[ "$#" -lt 1 ]]; then echo >&2 "usage: $(basename "$0") <tests-dir>" exit 1 fi -{- cd "$1"- for x in VMTests/*/*; do- echo >&2 "$x"- echo -n "$x " ; hevm vm-test --file $x- done-} | {- while read path test outcome; do- category=$(dirname "$path")- testcase=$(basename "${path%.json}")- row="<tr><td class=testcase>$testcase<td>$outcome<td class=category>$category"- row+=$'\n'- case $outcome in- ok) passed+=$row ;;- *) failed+=$row ;;- esac- done- - cat <<.+tests=$1+html=$2+match=$3+skip=$4+timeout=${5:-10}++_html() {+cat <<. <!doctype html> <title>hevm test results</title> <style>-* {- font-family:- "latin modern mono", "fantasque sans mono",- inconsolata, menlo, monospace;- font-size: 22px;- line-height: 26px;-}-+* { font-family:+"latin modern mono", "fantasque sans mono",+inconsolata, menlo, monospace;+font-size: 22px;+line-height: 26px; } body { margin: 2rem; } header { text-align: center; margin: 4rem 0; } table { border-collapse: collapse; width: 100%; }@@ -47,36 +38,73 @@ .testcase { font-weight: bold } #failed .testcase { color: rgb(200, 0, 0) } #passed .testcase { color: rgb(0, 150, 0) }+#skipped .testcase { color: rgb(0, 100, 250) } </style> <header> <h1>hevm consensus test report</h1>-<p>-$(date +%Y-%m-%d)-<p>-.-- wc -l <<<"$passed"- echo "passed, "- wc -l <<<"$failed"- echo "failed"- - cat <<.-<p>-(Test suite: <span class=VMTests</span>VMTests</span> for Homestead)+<p>$(date +%Y-%m-%d)</p>+<p>$(echo "$npass passed, $nfail failed, $nskip skipped")</p>+(Test suite: <span class=VMTests</span>VMTests</span> for ConstantinopleFix) </header> <h2>Failed tests</h2> <table id=failed> <tbody>-.- echo "$failed"- cat <<.+$(echo $failed) </table>+<h2>Skipped tests</h2>+<table id=skipped>+<tbody>+$(echo $skipped)+</table> <h2>Passed tests</h2> <table id=passed> <tbody>-.- echo "$passed"- cat <<.+$(echo $passed) </table> .+}++{+ cd "$tests"+ for x in VMTests/*/*; do+ if [[ $x =~ .*$match.* ]] && [[ -n $skip && $x =~ .*$skip.* ]]; then+ for job in $(<$x jq '.|keys[]' -r); do+ echo "$x $job skip"+ done+ elif [[ $x =~ .*$match.* ]]; then+ echo -n "$x " ; "$HEVM" vm-test --file $x --timeout $timeout 2>&1+ fi+ done+} | {+ while read path test outcome; do+ echo >&2 "$path $test $outcome"+ category=$(dirname "$path")+ testcase=$(basename "${path%.json}")+ row="<tr><td class=testcase>$testcase<td>$outcome<td class=category>$category"+ row+=$'\n'+ case $outcome in+ ok) passed+=$row ;;+ skip) skipped+=$row ;;+ timeout) timouts+=row ;;+ *) failed+=$row ;;+ esac+ done++ sum () { echo -ne "$1" | wc -l | awk '{print $1}'; }++ nfail=$(sum "$failed")+ npass=$(sum "$passed")+ nskip=$(sum "$skipped")+ ntime=$(sum "$timeouts")++ echo >&2 "passed: $npass"+ echo >&2 "failed: $nfail"+ echo >&2 "timeout: $ntime"+ echo >&2 "skipped: $nskip"++ if [[ $html == "True" ]]; then+ _html+ fi++ [[ $nfail -gt 0 ]] && exit 1 || exit 0 }
@@ -1,1719 +1,2609 @@ {-# Language ImplicitParams #-} {-# Language ConstraintKinds #-} {-# Language FlexibleInstances #-}-{-# Language GADTs #-}-{-# Language RecordWildCards #-}-{-# Language ScopedTypeVariables #-}-{-# Language StandaloneDeriving #-}-{-# Language StrictData #-}-{-# Language TemplateHaskell #-}-{-# Language TypeOperators #-}-{-# Language ViewPatterns #-}--module EVM where--import Prelude hiding ((^), log, Word, exponent)--import EVM.ABI-import EVM.Types-import EVM.Solidity-import EVM.Keccak-import EVM.Concrete-import EVM.Op-import EVM.FeeSchedule (FeeSchedule (..))--import qualified EVM.Precompiled--import Data.Binary.Get (runGetOrFail)-import Data.Bits (bit, testBit, complement)-import Data.Bits (xor, shiftR, (.&.), (.|.), FiniteBits (..))-import Data.Text (Text)-import Data.Word (Word8, Word32)--import Control.Lens hiding (op, (:<), (|>))-import Control.Monad.State.Strict hiding (state)--import Data.ByteString (ByteString)-import Data.ByteString.Lazy (fromStrict)-import Data.Map.Strict (Map)-import Data.Maybe (fromMaybe, isNothing)-import Data.Semigroup (Semigroup (..))-import Data.Sequence (Seq)-import Data.Vector.Storable (Vector)-import Data.Foldable (toList)--import Data.Tree--import qualified Data.ByteString as BS-import qualified Data.ByteString.Char8 as Char8-import qualified Data.Map.Strict as Map-import qualified Data.Sequence as Seq-import qualified Data.Tree.Zipper as Zipper-import qualified Data.Vector.Storable as Vector-import qualified Data.Vector.Storable.Mutable as Vector--import qualified Data.Vector as RegularVector---- * Data types--data Error- = BalanceTooLow Word Word- | UnrecognizedOpcode Word8- | SelfDestruction- | StackUnderrun- | BadJumpDestination- | Revert ByteString- | NoSuchContract Addr- | OutOfGas Word Word- | BadCheatCode Word32- | StackLimitExceeded- | IllegalOverflow- | Query Query- | PrecompiledContractError Int- | StateChangeWhileStatic--deriving instance Show Error---- | The possible result states of a VM-data VMResult- = VMFailure Error -- ^ An operation failed- | VMSuccess ByteString -- ^ Reached STOP, RETURN, or end-of-code--deriving instance Show VMResult---- | The state of a stepwise EVM execution-data VM = VM- { _result :: Maybe VMResult- , _state :: FrameState- , _frames :: [Frame]- , _env :: Env- , _block :: Block- , _tx :: TxState- , _logs :: Seq Log- , _traces :: Zipper.TreePos Zipper.Empty Trace- , _cache :: Cache- , _execMode :: ExecMode- , _burned :: Word- }--data Trace = Trace- { _traceCodehash :: W256- , _traceOpIx :: Int- , _traceData :: TraceData- }--data TraceData- = EventTrace Log- | FrameTrace FrameContext- | QueryTrace Query- | ErrorTrace Error- | EntryTrace Text- | ReturnTrace ByteString FrameContext--data ExecMode = ExecuteNormally | ExecuteAsVMTest--data Query where- PleaseFetchContract :: Addr -> (Contract -> EVM ()) -> Query- PleaseFetchSlot :: Addr -> Word -> (Word -> EVM ()) -> Query--instance Show Query where- showsPrec _ = \case- PleaseFetchContract addr _ ->- (("<EVM.Query: fetch contract " ++ show addr ++ ">") ++)- PleaseFetchSlot addr slot _ ->- (("<EVM.Query: fetch slot "- ++ show slot ++ " for "- ++ show addr ++ ">") ++)---- | Alias for the type of e.g. @exec1@.-type EVM a = State VM a---- | The cache is data that can be persisted for efficiency:--- any expensive query that is constant at least within a block.-data Cache = Cache- { _fetched :: Map Addr Contract- }---- | A way to specify an initial VM state-data VMOpts = VMOpts- { vmoptCode :: ByteString- , vmoptCalldata :: ByteString- , vmoptValue :: W256- , vmoptAddress :: Addr- , vmoptCaller :: Addr- , vmoptOrigin :: Addr- , vmoptGas :: W256- , vmoptNumber :: W256- , vmoptTimestamp :: W256- , vmoptCoinbase :: Addr- , vmoptDifficulty :: W256- , vmoptGaslimit :: W256- , vmoptGasprice :: W256- , vmoptSchedule :: FeeSchedule Word- } deriving Show---- | A log entry-data Log = Log Addr ByteString [Word]---- | An entry in the VM's "call/create stack"-data Frame = Frame- { _frameContext :: FrameContext- , _frameState :: FrameState- }---- | Call/create info-data FrameContext- = CreationContext- { creationContextCodehash :: W256 }- | CallContext- { callContextOffset :: Word- , callContextSize :: Word- , callContextCodehash :: W256- , callContextAbi :: Maybe Word- , callContextData :: ByteString- , callContextReversion :: Map Addr Contract- }---- | The "registers" of the VM along with memory and data stack-data FrameState = FrameState- { _contract :: Addr- , _codeContract :: Addr- , _code :: ByteString- , _pc :: Int- , _stack :: [Word]- , _memory :: ByteString- , _memorySize :: Int- , _calldata :: ByteString- , _callvalue :: Word- , _caller :: Addr- , _gas :: Word- , _returndata :: ByteString- , _static :: Bool- }---- | The state that spans a whole transaction-data TxState = TxState- { _selfdestructs :: [Addr]- , _refunds :: [(Addr, Word)]- }---- | The state of a contract-data Contract = Contract- { _bytecode :: ByteString- , _storage :: Map Word Word- , _balance :: Word- , _nonce :: Word- , _codehash :: W256- , _codesize :: Int -- (redundant?)- , _opIxMap :: Vector Int- , _codeOps :: RegularVector.Vector (Int, Op)- , _external :: Bool- }--deriving instance Show Contract-deriving instance Eq Contract---- | Various environmental data-data Env = Env- { _contracts :: Map Addr Contract- , _sha3Crack :: Map Word ByteString- , _origin :: Addr- }----- | Data about the block-data Block = Block- { _coinbase :: Addr- , _timestamp :: Word- , _number :: Word- , _difficulty :: Word- , _gaslimit :: Word- , _gasprice :: Word- , _schedule :: FeeSchedule Word- }--blankState :: FrameState-blankState = FrameState- { _contract = 0- , _codeContract = 0- , _code = mempty- , _pc = 0- , _stack = mempty- , _memory = mempty- , _memorySize = 0- , _calldata = mempty- , _callvalue = 0- , _caller = 0- , _gas = 0- , _returndata = mempty- , _static = False- }--makeLenses ''FrameState-makeLenses ''Frame-makeLenses ''Block-makeLenses ''TxState-makeLenses ''Contract-makeLenses ''Env-makeLenses ''Cache-makeLenses ''Trace-makeLenses ''VM--instance Semigroup Cache where- a <> b = Cache- { _fetched = mappend (view fetched a) (view fetched b) }--instance Monoid Cache where- mempty = Cache { _fetched = mempty }---- * Data accessors--currentContract :: VM -> Maybe Contract-currentContract vm =- view (env . contracts . at (view (state . codeContract) vm)) vm---- * Data constructors--makeVm :: VMOpts -> VM-makeVm o = VM- { _result = Nothing- , _frames = mempty- , _tx = TxState- { _selfdestructs = mempty- , _refunds = mempty- }- , _logs = mempty- , _traces = Zipper.fromForest []- , _block = Block- { _coinbase = vmoptCoinbase o- , _timestamp = w256 $ vmoptTimestamp o- , _number = w256 $ vmoptNumber o- , _difficulty = w256 $ vmoptDifficulty o- , _gaslimit = w256 $ vmoptGaslimit o- , _gasprice = w256 $ vmoptGasprice o- , _schedule = vmoptSchedule o- }- , _state = FrameState- { _pc = 0- , _stack = mempty- , _memory = mempty- , _memorySize = 0- , _code = vmoptCode o- , _contract = vmoptAddress o- , _codeContract = vmoptAddress o- , _calldata = vmoptCalldata o- , _callvalue = w256 $ vmoptValue o- , _caller = vmoptCaller o- , _gas = w256 $ vmoptGas o- , _returndata = mempty- , _static = False- }- , _env = Env- { _sha3Crack = mempty- , _origin = vmoptOrigin o- , _contracts = Map.fromList- [(vmoptAddress o, initialContract (vmoptCode o))]- }- , _cache = mempty- , _execMode = ExecuteNormally- , _burned = 0- }--initialContract :: ByteString -> Contract-initialContract theCode = Contract- { _bytecode = theCode- , _codesize = BS.length theCode- , _codehash =- if BS.null theCode then 0 else- keccak (stripBytecodeMetadata theCode)- , _storage = mempty- , _balance = 0- , _nonce = 0- , _opIxMap = mkOpIxMap theCode- , _codeOps = mkCodeOps theCode- , _external = False- }---- * Opcode dispatch (exec1)--next :: (?op :: Word8) => EVM ()-next = modifying (state . pc) (+ (opSize ?op))--exec1 :: EVM ()-exec1 = do- vm <- get-- let- -- Convenience function to access parts of the current VM state.- -- Arcane type signature needed to avoid monomorphism restriction.- the :: (b -> VM -> Const a VM) -> ((a -> Const a a) -> b) -> a- the f g = view (f . g) vm-- -- Convenient aliases- mem = the state memory- stk = the state stack- self = the state contract- this = fromMaybe (error "internal error: state contract") (preview (ix (the state contract)) (the env contracts))-- fees@(FeeSchedule {..}) = the block schedule-- doStop = finishFrame (FrameReturned "")-- if the state pc >= num (BS.length (the state code))- then- doStop-- else do- let ?op = BS.index (the state code) (the state pc)-- case ?op of-- -- op: PUSH- x | x >= 0x60 && x <= 0x7f -> do- let !n = num x - 0x60 + 1- !xs = BS.take n (BS.drop (1 + the state pc)- (the state code))- limitStack 1 $- burn g_verylow $ do- next- push (w256 (word xs))-- -- op: DUP- x | x >= 0x80 && x <= 0x8f -> do- let !i = x - 0x80 + 1- case preview (ix (num i - 1)) stk of- Nothing -> underrun- Just y -> do- limitStack 1 $- burn g_verylow $ do- next- push y-- -- op: SWAP- x | x >= 0x90 && x <= 0x9f -> do- let i = num (x - 0x90 + 1)- if length stk < i + 1- then underrun- else- burn g_verylow $ do- next- zoom (state . stack) $ do- assign (ix 0) (stk ^?! ix i)- assign (ix i) (stk ^?! ix 0)-- -- op: LOG- x | x >= 0xa0 && x <= 0xa4 ->- notStatic $- let n = (num x - 0xa0) in- case stk of- (xOffset:xSize:xs) ->- if length xs < n- then underrun- else do- let (topics, xs') = splitAt n xs- bytes = readMemory (num xOffset) (num xSize) vm- log = Log self bytes topics-- burn (g_log + g_logdata * xSize + num n * g_logtopic) $ do- accessMemoryRange fees xOffset xSize $ do- traceLog log- next- assign (state . stack) xs'- pushToSequence logs log- _ ->- underrun-- -- op: STOP- 0x00 -> doStop-- -- op: ADD- 0x01 -> stackOp2 (const g_verylow) (uncurry (+))- -- op: MUL- 0x02 -> stackOp2 (const g_low) (uncurry (*))- -- op: SUB- 0x03 -> stackOp2 (const g_verylow) (uncurry (-))-- -- op: DIV- 0x04 -> stackOp2 (const g_low) $- \case (_, 0) -> 0- (x, y) -> div x y-- -- op: SDIV- 0x05 ->- stackOp2 (const g_low) (uncurry (sdiv))-- -- op: MOD- 0x06 -> stackOp2 (const g_low) $ \case- (_, 0) -> 0- (x, y) -> mod x y-- -- op: SMOD- 0x07 -> stackOp2 (const g_low) $ uncurry smod- -- op: ADDMOD- 0x08 -> stackOp3 (const g_mid) $ (\(x, y, z) -> addmod x y z)- -- op: MULMOD- 0x09 -> stackOp3 (const g_mid) $ (\(x, y, z) -> mulmod x y z)-- -- op: LT- 0x10 -> stackOp2 (const g_verylow) $ \(x, y) -> if x < y then 1 else 0- -- op: GT- 0x11 -> stackOp2 (const g_verylow) $ \(x, y) -> if x > y then 1 else 0- -- op: SLT- 0x12 -> stackOp2 (const g_verylow) $ uncurry slt- -- op: SGT- 0x13 -> stackOp2 (const g_verylow) $ uncurry sgt-- -- op: EQ- 0x14 -> stackOp2 (const g_verylow) $ \(x, y) -> if x == y then 1 else 0- -- op: ISZERO- 0x15 -> stackOp1 (const g_verylow) $ \case 0 -> 1; _ -> 0-- -- op: AND- 0x16 -> stackOp2 (const g_verylow) $ uncurry (.&.)- -- op: OR- 0x17 -> stackOp2 (const g_verylow) $ uncurry (.|.)- -- op: XOR- 0x18 -> stackOp2 (const g_verylow) $ uncurry xor- -- op: NOT- 0x19 -> stackOp1 (const g_verylow) complement-- -- op: BYTE- 0x1a -> stackOp2 (const g_verylow) $ \case- (n, _) | n >= 32 ->- 0- (n, x) ->- 0xff .&. shiftR x (8 * (31 - num n))-- -- op: SHA3- 0x20 ->- case stk of- ((num -> xOffset) : (num -> xSize) : xs) -> do- let bytes = readMemory xOffset xSize vm- hash = keccakBlob bytes- burn (g_sha3 + g_sha3word * ceilDiv (num xSize) 32) $- accessMemoryRange fees xOffset xSize $ do- next- assign (state . stack) (hash : xs)- assign (env . sha3Crack . at hash) (Just bytes)- _ -> underrun-- -- op: ADDRESS- 0x30 ->- limitStack 1 $- burn g_base (next >> push (num (the state contract)))-- -- op: BALANCE- 0x31 ->- case stk of- (x:xs) -> do- burn g_balance $ do- touchAccount (num x) $ \c -> do- next- assign (state . stack) xs- push (view balance c)- [] ->- underrun-- -- op: ORIGIN- 0x32 ->- limitStack 1 . burn g_base $- next >> push (num (the env origin))-- -- op: CALLER- 0x33 ->- limitStack 1 . burn g_base $- next >> push (num (the state caller))-- -- op: CALLVALUE- 0x34 ->- limitStack 1 . burn g_base $- next >> push (the state callvalue)-- -- op: CALLDATALOAD- 0x35 -> stackOp1 (const g_verylow) $- \x -> readBlobWord x (the state calldata)-- -- op: CALLDATASIZE- 0x36 ->- limitStack 1 . burn g_base $- next >> push (blobSize (the state calldata))-- -- op: CALLDATACOPY- 0x37 ->- case stk of- ((num -> xTo) : (num -> xFrom) : (num -> xSize) :xs) -> do- burn (g_verylow + g_copy * ceilDiv xSize 32) $ do- accessMemoryRange fees xTo xSize $ do- next- assign (state . stack) xs- copyBytesToMemory (the state calldata) xSize xFrom xTo- _ -> underrun-- -- op: CODESIZE- 0x38 ->- limitStack 1 . burn g_base $- next >> push (num (BS.length (the state code)))-- -- op: CODECOPY- 0x39 ->- case stk of- ((num -> memOffset) : (num -> codeOffset) : (num -> n) : xs) -> do- burn (g_verylow + g_copy * ceilDiv (num n) 32) $ do- accessMemoryRange fees memOffset n $ do- next- assign (state . stack) xs- copyBytesToMemory (view bytecode this)- n codeOffset memOffset- _ -> underrun-- -- op: GASPRICE- 0x3a ->- limitStack 1 . burn g_base $- next >> push (the block gasprice)-- -- op: EXTCODESIZE- 0x3b ->- case stk of- (x:xs) -> do- if x == num cheatCode- then do- next- assign (state . stack) xs- push (w256 1)- else- burn g_extcode $ do- touchAccount (num x) $ \c -> do- next- assign (state . stack) xs- push (num (view codesize c))- [] ->- underrun-- -- op: EXTCODECOPY- 0x3c ->- case stk of- ( extAccount- : (num -> memOffset)- : (num -> codeOffset)- : (num -> codeSize)- : xs ) -> do- burn (g_extcode + g_copy * ceilDiv (num codeSize) 32) $- accessMemoryRange fees memOffset codeSize $ do- touchAccount (num extAccount) $ \c -> do- next- assign (state . stack) xs- copyBytesToMemory (view bytecode c)- codeSize codeOffset memOffset- _ -> underrun-- -- op: RETURNDATASIZE- 0x3d ->- limitStack 1 . burn g_base $- next >> push (blobSize (the state returndata))-- -- op: RETURNDATACOPY- 0x3e ->- case stk of- ((num -> xTo) : (num -> xFrom) : (num -> xSize) :xs) -> do- burn (g_verylow + g_copy * ceilDiv xSize 32) $ do- accessMemoryRange fees xTo xSize $ do- next- assign (state . stack) xs- copyBytesToMemory (the state returndata) xSize xFrom xTo- _ -> underrun-- -- op: BLOCKHASH- 0x40 -> do- -- We adopt the fake block hash scheme of the VMTests,- -- so that blockhash(i) is the hash of i as decimal ASCII.- stackOp1 (const g_blockhash) $- \i ->- if i + 256 < the block number || i >= the block number- then 0- else- (num i :: Integer)- & show & Char8.pack & keccak & num-- -- op: COINBASE- 0x41 ->- limitStack 1 . burn g_base $- next >> push (num (the block coinbase))-- -- op: TIMESTAMP- 0x42 ->- limitStack 1 . burn g_base $- next >> push (the block timestamp)-- -- op: NUMBER- 0x43 ->- limitStack 1 . burn g_base $- next >> push (the block number)-- -- op: DIFFICULTY- 0x44 ->- limitStack 1 . burn g_base $- next >> push (the block difficulty)-- -- op: GASLIMIT- 0x45 ->- limitStack 1 . burn g_base $- next >> push (the block gaslimit)-- -- op: POP- 0x50 ->- case stk of- (_:xs) -> burn g_base (next >> assign (state . stack) xs)- _ -> underrun-- -- op: MLOAD- 0x51 ->- case stk of- (x:xs) -> do- burn g_verylow $- accessMemoryWord fees x $ do- next- assign (state . stack) (view (word256At (num x)) mem : xs)- _ -> underrun-- -- op: MSTORE- 0x52 ->- case stk of- (x:y:xs) -> do- burn g_verylow $- accessMemoryWord fees x $ do- next- assign (state . memory . word256At (num x)) y- assign (state . stack) xs- _ -> underrun-- -- op: MSTORE8- 0x53 ->- case stk of- (x:y:xs) -> do- burn g_verylow $- accessMemoryRange fees x 1 $ do- next- modifying (state . memory) (setMemoryByte x (wordToByte y))- assign (state . stack) xs- _ -> underrun-- -- op: SLOAD- 0x54 ->- case stk of- (x:xs) ->- burn g_sload $- accessStorage self x $ \y -> do- next- assign (state . stack) (y:xs)- _ -> underrun-- -- op: SSTORE- 0x55 ->- notStatic $- case stk of- (x:new:xs) -> do- accessStorage self x $ \old -> do- -- Gas cost is higher when changing from zero to nonzero.- let cost = if old == 0 && new /= 0 then g_sset else g_sreset-- burn cost $ do- next- assign (state . stack) xs- assign (env . contracts . ix (the state contract) . storage . at x)- (Just new)-- -- Give gas refund if clearing the storage slot.- if old /= 0 && new == 0 then refund r_sclear else noop-- _ -> underrun-- -- op: JUMP- 0x56 ->- case stk of- (x:xs) -> do- burn g_mid $ do- checkJump x xs- _ -> underrun-- -- op: JUMPI- 0x57 -> do- case stk of- (x:y:xs) -> do- burn g_high $ do- if y == 0- then assign (state . stack) xs >> next- else checkJump x xs- _ -> underrun-- -- op: PC- 0x58 ->- limitStack 1 . burn g_base $- next >> push (num (the state pc))-- -- op: MSIZE- 0x59 ->- limitStack 1 . burn g_base $- next >> push (num (the state memorySize))-- -- op: GAS- 0x5a ->- limitStack 1 . burn g_base $- next >> push (the state gas - g_base)-- -- op: JUMPDEST- 0x5b -> burn g_jumpdest next-- -- op: EXP- 0x0a ->- let cost (_, exponent) =- if exponent == 0- then g_exp- else g_exp + g_expbyte * num (ceilDiv (1 + log2 exponent) 8)- in stackOp2 cost (uncurry exponentiate)-- -- op: SIGNEXTEND- 0x0b ->- stackOp2 (const g_low) $ \(bytes, x) ->- if bytes >= 32 then x- else let n = num bytes * 8 + 7 in- if testBit x n- then x .|. complement (bit n - 1)- else x .&. (bit n - 1)-- -- op: CREATE- 0xf0 ->- notStatic $- case stk of- (xValue:xOffset:xSize:xs) ->- burn g_create $ do- accessMemoryRange fees xOffset xSize $ do- if xValue > view balance this- then do- assign (state . stack) (0 : xs)- next- else do- let- newAddr =- newContractAddress self (wordValue (view nonce this))- case view execMode vm of- ExecuteAsVMTest -> do- assign (state . stack) (num newAddr : xs)- next-- ExecuteNormally -> do- let- newCode =- readMemory (num xOffset) (num xSize) vm- newContract =- initialContract newCode- newContext =- CreationContext (view codehash newContract)-- zoom (env . contracts) $ do- assign (at newAddr) (Just newContract)- assign (ix newAddr . balance) xValue- modifying (ix self . balance) (flip (-) xValue)- modifying (ix self . nonce) succ-- pushTrace (FrameTrace newContext)- next- vm' <- get- pushTo frames $ Frame- { _frameContext = newContext- , _frameState = (set stack xs) (view state vm')- }-- assign state $- blankState- & set contract newAddr- & set codeContract newAddr- & set code newCode- & set callvalue xValue- & set caller self- & set gas (view (state . gas) vm')-- _ -> underrun-- -- op: CALL- 0xf1 ->- case stk of- ( xGas- : (num -> xTo)- : xValue- : xInOffset- : xInSize- : xOutOffset- : xOutSize- : xs- ) ->- case xTo of- n | n > 0 && n <= 8 -> precompiledContract- n | num n == cheatCode ->- do- assign (state . stack) xs- cheat (xInOffset, xInSize) (xOutOffset, xOutSize)- _ ->- let- availableGas = the state gas- recipient = view (env . contracts . at xTo) vm- (cost, gas') = costOfCall fees recipient xValue availableGas xGas- in burn (cost - gas') $- (if xValue > 0 then notStatic else id) $- if xValue > view balance this- then do- assign (state . stack) (0 : xs)- next- else- case view execMode vm of- ExecuteAsVMTest -> do- assign (state . stack) (1 : xs)- next- ExecuteNormally -> do- delegateCall fees gas' xTo xInOffset xInSize xOutOffset xOutSize xs $ do- zoom state $ do- assign callvalue xValue- assign caller (the state contract)- assign contract xTo- assign memorySize 0- zoom (env . contracts) $ do- ix self . balance -= xValue- ix xTo . balance += xValue- _ ->- underrun-- -- op: CALLCODE- 0xf2 ->- error "CALLCODE not supported (use DELEGATECALL)"-- -- op: RETURN- 0xf3 ->- case stk of- (xOffset:xSize:_) ->- accessMemoryRange fees xOffset xSize $ do- let output = readMemory (num xOffset) (num xSize) vm- finishFrame (FrameReturned output)- _ -> underrun-- -- op: DELEGATECALL- 0xf4 ->- case stk of- (xGas:xTo:xInOffset:xInSize:xOutOffset:xOutSize:xs) ->- if num xTo == cheatCode- then do- assign (state . stack) xs- cheat (xInOffset, xInSize) (xOutOffset, xOutSize)- else- burn (num g_call) $ do- delegateCall fees xGas (num xTo) xInOffset xInSize xOutOffset xOutSize xs- (return ())- _ -> underrun-- -- op: STATICCALL- 0xfa ->- case stk of- (xGas : (num -> xTo) : xInOffset : xInSize : xOutOffset : xOutSize : xs) ->- case xTo of- n | n > 0 && n <= 8 -> precompiledContract- _ ->- let- availableGas = the state gas- recipient = view (env . contracts . at xTo) vm- (cost, gas') = costOfCall fees recipient 0 availableGas xGas- in burn (cost - gas') $- case view execMode vm of- ExecuteAsVMTest -> do- assign (state . stack) (1 : xs)- next- ExecuteNormally -> do- delegateCall fees gas' xTo xInOffset xInSize xOutOffset xOutSize xs $ do- zoom state $ do- assign callvalue 0- assign caller (the state contract)- assign contract xTo- assign memorySize 0- assign static True- _ ->- underrun-- -- op: SELFDESTRUCT- 0xff ->- notStatic $- case stk of- [] -> underrun- (x:_) -> do- touchAccount (num x) $ \_ -> do- pushTo (tx . selfdestructs) self- assign (env . contracts . ix self . balance) 0- modifying- (env . contracts . ix (num x) . balance)- (+ (vm ^?! env . contracts . ix self . balance))- doStop-- -- op: REVERT- 0xfd ->- case stk of- (xOffset:xSize:_) ->- accessMemoryRange fees xOffset xSize $ do- let output = readMemory (num xOffset) (num xSize) vm- finishFrame (FrameReverted output)- _ -> underrun-- xxx ->- vmError (UnrecognizedOpcode xxx)--precompiledContract :: (?op :: Word8) => EVM ()-precompiledContract = do- vm <- get- fees <- use (block . schedule)- stk <- use (state . stack)-- case (?op, stk) of- -- CALL (includes value)- (0xf1, (_:(num -> op):_:inOffset:inSize:outOffset:outSize:xs)) ->- doIt vm fees op inOffset inSize outOffset outSize xs- -- STATICCALL (does not include value)- (0xfa, (_:(num -> op):inOffset:inSize:outOffset:outSize:xs)) ->- doIt vm fees op inOffset inSize outOffset outSize xs- _ ->- underrun-- where- doIt vm fees op inOffset inSize outOffset outSize xs =- let- input = readMemory (num inOffset) (num inSize) vm- in- case EVM.Precompiled.execute op input (num outSize) of- Nothing -> do- assign (state . stack) (0 : xs)- vmError (PrecompiledContractError op)- Just output -> do- let- cost =- case op of- 1 -> 3000- _ -> error ("unimplemented precompiled contract " ++ show op)- accessMemoryRange fees inOffset inSize $- accessMemoryRange fees outOffset outSize $- burn cost $ do- assign (state . stack) (1 : xs)- modifying (state . memory)- (writeMemory output outSize 0 outOffset)- next---- * Opcode helper actions--noop :: Monad m => m ()-noop = pure ()--pushTo :: MonadState s m => ASetter s s [a] [a] -> a -> m ()-pushTo f x = f %= (x :)--pushToSequence :: MonadState s m => ASetter s s (Seq a) (Seq a) -> a -> m ()-pushToSequence f x = f %= (Seq.|> x)--touchAccount :: Addr -> (Contract -> EVM ()) -> EVM ()-touchAccount addr continue = do- use (env . contracts . at addr) >>= \case- Just c -> continue c- Nothing ->- use (cache . fetched . at addr) >>= \case- Just c -> do- assign (env . contracts . at addr) (Just c)- continue c- Nothing ->- assign result . Just . VMFailure . Query $- PleaseFetchContract addr- (\c -> do assign (cache . fetched . at addr) (Just c)- assign (env . contracts . at addr) (Just c)- assign result Nothing- continue c)--accessStorage- :: Addr -- ^ Contract address- -> Word -- ^ Storage slot key- -> (Word -> EVM ()) -- ^ Continuation- -> EVM ()-accessStorage addr slot continue =- use (env . contracts . at addr) >>= \case- Just c ->- case view (storage . at slot) c of- Just value ->- continue value- Nothing ->- if view external c- then- assign result . Just . VMFailure . Query $- PleaseFetchSlot addr slot- (\x -> do- assign (cache . fetched . ix addr . storage . at slot) (Just x)- assign (env . contracts . ix addr . storage . at slot) (Just x)- assign result Nothing- continue x)- else do- assign (env . contracts . ix addr . storage . at slot) (Just 0)- continue 0- Nothing ->- touchAccount addr $ \_ ->- accessStorage addr slot continue---- | Replace a contract's code, like when CREATE returns--- from the constructor code.-replaceCode :: Addr -> ByteString -> EVM ()-replaceCode target newCode = do- zoom (env . contracts . at target) $ do- if BS.null newCode- then put Nothing- else do- Just now <- get- put . Just $- initialContract newCode- & set storage (view storage now)- & set balance (view balance now)- & set nonce (view nonce now)--replaceCodeOfSelf :: ByteString -> EVM ()-replaceCodeOfSelf newCode = do- vm <- get- replaceCode (view (state . contract) vm) newCode--resetState :: EVM ()-resetState = do- assign result Nothing- assign frames []- assign state blankState--finalize :: EVM ()-finalize = do- destroyedAddresses <- use (tx . selfdestructs)- modifying (env . contracts)- (Map.filterWithKey (\k _ -> not (elem k destroyedAddresses)))--loadContract :: Addr -> EVM ()-loadContract target =- preuse (env . contracts . ix target . bytecode) >>=- \case- Nothing ->- error "Call target doesn't exist"- Just targetCode -> do- assign (state . contract) target- assign (state . code) targetCode- assign (state . codeContract) target--limitStack :: Int -> EVM () -> EVM ()-limitStack n continue = do- stk <- use (state . stack)- if length stk + n > 1024- then vmError StackLimitExceeded- else continue--notStatic :: EVM () -> EVM ()-notStatic continue = do- bad <- use (state . static)- if bad- then vmError StateChangeWhileStatic- else continue--burn :: Word -> EVM () -> EVM ()-burn n continue = do- available <- use (state . gas)- if n <= available- then do- state . gas -= n- burned += n- continue- else- vmError (OutOfGas available n)--refund :: Word -> EVM ()-refund n = do- self <- use (state . contract)- pushTo (tx . refunds) (self, n)----- * Cheat codes---- The cheat code is 7109709ecfa91a80626ff3989d68f67f5b1dd12d.--- Call this address using one of the cheatActions below to do--- special things, e.g. changing the block timestamp. Beware that--- these are necessarily hevm specific.-cheatCode :: Addr-cheatCode = num (keccak "hevm cheat code")--cheat- :: (?op :: Word8)- => (Word, Word) -> (Word, Word)- -> EVM ()-cheat (inOffset, inSize) (outOffset, outSize) = do- mem <- use (state . memory)- let- abi =- num (wordValue (readMemoryWord32 inOffset mem))- input =- sliceMemory (inOffset + 4) (inSize - 4) mem- case Map.lookup abi cheatActions of- Nothing ->- vmError (BadCheatCode abi)- Just (argTypes, action) -> do- case runGetOrFail- (getAbiSeq (length argTypes) argTypes)- (fromStrict input) of- Right ("", _, args) -> do- action (toList args) >>= \case- Nothing -> do- next- push 1- Just (encodeAbiValue -> bs) -> do- next- modifying (state . memory)- (writeMemory bs outSize 0 outOffset)- push 1- Left _ ->- vmError (BadCheatCode abi)- Right _ ->- vmError (BadCheatCode abi)--type CheatAction = ([AbiType], [AbiValue] -> EVM (Maybe AbiValue))--cheatActions :: Map Word32 CheatAction-cheatActions =- Map.fromList- [ action "warp(uint256)" [AbiUIntType 256] $- \[AbiUInt 256 x] -> do- assign (block . timestamp) (w256 (W256 x))- return Nothing- ]- where- action s ts f = (abiKeccak s, (ts, f))----- * General call implementation ("delegateCall")--delegateCall- :: (?op :: Word8)- => FeeSchedule Word- -> Word -> Addr -> Word -> Word -> Word -> Word -> [Word]- -> EVM ()- -> EVM ()-delegateCall fees xGas xTo xInOffset xInSize xOutOffset xOutSize xs continue =- touchAccount xTo . const $- preuse (env . contracts . ix xTo) >>=- \case- Nothing -> vmError (NoSuchContract xTo)- Just target ->- accessMemoryRange fees xInOffset xInSize $ do- accessMemoryRange fees xOutOffset xOutSize $ do- burn xGas $ do- vm0 <- get-- let newContext = CallContext- { callContextOffset = xOutOffset- , callContextSize = xOutSize- , callContextCodehash = view codehash target- , callContextReversion = view (env . contracts) vm0- , callContextAbi =- if xInSize >= 4- then- let- w = wordValue- (readMemoryWord32 xInOffset (view (state . memory) vm0))- in Just $! num w- else Nothing- , callContextData = (readMemory (num xInOffset) (num xInSize) vm0)- }-- pushTrace (FrameTrace newContext)- next- vm1 <- get-- pushTo frames $ Frame- { _frameState = (set stack xs) (view state vm1)- , _frameContext = newContext- }-- zoom state $ do- assign gas xGas- assign pc 0- assign code (view bytecode target)- assign codeContract xTo- assign stack mempty- assign memory mempty- assign calldata (readMemory (num xInOffset) (num xInSize) vm0)-- continue----- * VM error implementation--underrun :: EVM ()-underrun = vmError StackUnderrun---- | A stack frame can be popped in three ways.-data FrameResult- = FrameReturned ByteString -- ^ STOP, RETURN, or no more code- | FrameReverted ByteString -- ^ REVERT- | FrameErrored Error -- ^ Any other error- deriving Show---- | This function defines how to pop the current stack frame in either of--- the ways specified by 'FrameResult'.------ It also handles the case when the current stack frame is the only one;--- in this case, we set the final '_result' of the VM execution.-finishFrame :: FrameResult -> EVM ()-finishFrame how = do- oldVm <- get-- case view frames oldVm of- -- Is the current frame the only one?- [] ->- assign result . Just $- case how of- FrameReturned output -> VMSuccess output- FrameReverted output -> VMFailure (Revert output)- FrameErrored e -> VMFailure e-- -- Are there some remaining frames?- nextFrame : remainingFrames -> do-- -- Pop the top frame.- assign frames remainingFrames- -- Install the state of the frame to which we shall return.- assign state (view frameState nextFrame)- -- Insert a debug trace.- insertTrace $- case how of- FrameErrored e ->- ErrorTrace e- FrameReverted output ->- ErrorTrace (Revert output)- FrameReturned output ->- ReturnTrace output (view frameContext nextFrame)- -- Pop to the previous level of the debug trace stack.- popTrace-- let remainingGas = view (state . gas) oldVm-- -- Now dispatch on whether we were creating or calling,- -- and whether we shall return, revert, or error (six cases).- case view frameContext nextFrame of-- -- Were we calling?- CallContext (num -> outOffset) (num -> outSize) _ _ _ reversion -> do-- let- -- When entering a call, the gas allowance is counted as burned- -- in advance; this unburns the remainder and adds it to the- -- parent frame.- reclaimRemainingGasAllowance = do- modifying burned (subtract remainingGas)- modifying (state . gas) (+ remainingGas)-- revertContracts = assign (env . contracts) reversion-- case how of- -- Case 1: Returning from a call?- FrameReturned output -> do- assign (state . returndata) output- copyBytesToMemory output outSize 0 outOffset- reclaimRemainingGasAllowance- push 1-- -- Case 2: Reverting during a call?- FrameReverted output -> do- revertContracts- assign (state . returndata) output- reclaimRemainingGasAllowance- push 0-- -- Case 3: Error during a call?- FrameErrored _ -> do- revertContracts- push 0-- -- Or were we creating?- CreationContext _ -> do- let- createe = view (state . contract) oldVm- destroy = assign (env . contracts . at createe) Nothing-- case how of- -- Case 4: Returning during a creation?- FrameReturned output -> do- replaceCode createe output- assign (state . gas) remainingGas- push (num createe)-- -- Case 5: Reverting during a creation?- FrameReverted output -> do- destroy- assign (state . returndata) output- assign (state . gas) remainingGas- push 0-- -- Case 6: Error during a creation?- FrameErrored _ -> do- destroy- assign (state . gas) 0- push 0---vmError :: Error -> EVM ()-vmError e = finishFrame (FrameErrored e)---- * Memory helpers--accessMemoryRange- :: FeeSchedule Word- -> Word- -> Word- -> EVM ()- -> EVM ()-accessMemoryRange _ _ 0 continue = continue-accessMemoryRange fees f l continue = do- m0 <- num <$> use (state . memorySize)- if f + l < l- then vmError IllegalOverflow- else do- let m1 = 32 * ceilDiv (max m0 (f + l)) 32- burn (memoryCost fees m1 - memoryCost fees m0) $ do- assign (state . memorySize) (num m1)- continue--accessMemoryWord- :: FeeSchedule Word -> Word -> EVM () -> EVM ()-accessMemoryWord fees x continue = accessMemoryRange fees x 32 continue--copyBytesToMemory- :: ByteString -> Word -> Word -> Word -> EVM ()-copyBytesToMemory bs size xOffset yOffset =- if size == 0 then noop- else do- mem <- use (state . memory)- assign (state . memory) $- writeMemory bs size xOffset yOffset mem--readMemory :: Word -> Word -> VM -> ByteString-readMemory offset size vm = sliceMemory offset size (view (state . memory) vm)--word256At- :: Functor f- => Word -> (Word -> f Word)- -> ByteString -> f ByteString-word256At i = lens getter setter where- getter m = readMemoryWord i m- setter m x = setMemoryWord i x m---- * Tracing--withTraceLocation- :: (MonadState VM m) => TraceData -> m Trace-withTraceLocation x = do- vm <- get- let- Just this =- preview (env . contracts . ix (view (state . codeContract) vm)) vm- pure Trace- { _traceData = x- , _traceCodehash = view codehash this- , _traceOpIx = (view opIxMap this) Vector.! (view (state . pc) vm)- }--pushTrace :: TraceData -> EVM ()-pushTrace x = do- trace <- withTraceLocation x- modifying traces $- \t -> Zipper.children $ Zipper.insert (Node trace []) t--insertTrace :: TraceData -> EVM ()-insertTrace x = do- trace <- withTraceLocation x- modifying traces $- \t -> Zipper.nextSpace $ Zipper.insert (Node trace []) t--popTrace :: EVM ()-popTrace =- modifying traces $- \t -> case Zipper.parent t of- Nothing -> error "internal error (trace root)"- Just t' -> Zipper.nextSpace t'--zipperRootForest :: Zipper.TreePos Zipper.Empty a -> Forest a-zipperRootForest z =- case Zipper.parent z of- Nothing -> Zipper.toForest z- Just z' -> zipperRootForest (Zipper.nextSpace z')--traceForest :: VM -> Forest Trace-traceForest vm =- view (traces . to zipperRootForest) vm--traceLog :: (MonadState VM m) => Log -> m ()-traceLog log = do- trace <- withTraceLocation (EventTrace log)- modifying traces $- \t -> Zipper.nextSpace (Zipper.insert (Node trace []) t)---- * Stack manipulation--push :: Word -> EVM ()-push x = state . stack %= (x :)--stackOp1- :: (?op :: Word8)- => (Word -> Word)- -> (Word -> Word)- -> EVM ()-stackOp1 cost f =- use (state . stack) >>= \case- (x:xs) ->- burn (cost x) $ do- next- let !y = f x- state . stack .= y : xs- _ ->- underrun--stackOp2- :: (?op :: Word8)- => ((Word, Word) -> Word)- -> ((Word, Word) -> Word)- -> EVM ()-stackOp2 cost f =- use (state . stack) >>= \case- (x:y:xs) ->- burn (cost (x, y)) $ do- next- state . stack .= f (x, y) : xs- _ ->- underrun--stackOp3- :: (?op :: Word8)- => ((Word, Word, Word) -> Word)- -> ((Word, Word, Word) -> Word)- -> EVM ()-stackOp3 cost f =- use (state . stack) >>= \case- (x:y:z:xs) ->- burn (cost (x, y, z)) $ do- next- state . stack .= f (x, y, z) : xs- _ ->- underrun---- * Bytecode data functions--checkJump :: (Integral n) => n -> [Word] -> EVM ()-checkJump x xs = do- theCode <- use (state . code)- if x < num (BS.length theCode) && BS.index theCode (num x) == 0x5b- then- insidePushData (num x) >>=- \case- True ->- vmError BadJumpDestination- _ -> do- state . stack .= xs- state . pc .= num x- else vmError BadJumpDestination--insidePushData :: Int -> EVM Bool-insidePushData i =- -- If the operation index for the code pointer is the same- -- as for the previous code pointer, then it's inside push data.- if i == 0- then pure False- else do- self <- use (state . codeContract)- Just x <- preuse (env . contracts . ix self . opIxMap)- pure ((x Vector.! i) == (x Vector.! (i - 1)))--opSize :: Word8 -> Int-opSize x | x >= 0x60 && x <= 0x7f = num x - 0x60 + 2-opSize _ = 1---- Index i of the resulting vector contains the operation index for--- the program counter value i. This is needed because source map--- entries are per operation, not per byte.-mkOpIxMap :: ByteString -> Vector Int-mkOpIxMap xs = Vector.create $ Vector.new (BS.length xs) >>= \v ->- -- Loop over the byte string accumulating a vector-mutating action.- -- This is somewhat obfuscated, but should be fast.- let (_, _, _, m) =- BS.foldl' (go v) (0 :: Word8, 0, 0, return ()) xs- in m >> return v- where- go v (0, !i, !j, !m) x | x >= 0x60 && x <= 0x7f =- {- Start of PUSH op. -} (x - 0x60 + 1, i + 1, j, m >> Vector.write v i j)- go v (1, !i, !j, !m) _ =- {- End of PUSH op. -} (0, i + 1, j + 1, m >> Vector.write v i j)- go v (0, !i, !j, !m) _ =- {- Other op. -} (0, i + 1, j + 1, m >> Vector.write v i j)- go v (n, !i, !j, !m) _ =- {- PUSH data. -} (n - 1, i + 1, j, m >> Vector.write v i j)--vmOp :: VM -> Maybe Op-vmOp vm =- let i = vm ^. state . pc- xs = BS.drop i (vm ^. state . code)- op = BS.index xs 0- in if BS.null xs- then Nothing- else Just (readOp op (BS.drop 1 xs))--vmOpIx :: VM -> Maybe Int-vmOpIx vm =- do self <- currentContract vm- (view opIxMap self) Vector.!? (view (state . pc) vm)--opParams :: VM -> Map String Word-opParams vm =- case vmOp vm of- Just OpCreate ->- params $ words "value offset size"- Just OpCall ->- params $ words "gas to value in-offset in-size out-offset out-size"- Just OpSstore ->- params $ words "index value"- Just OpCodecopy ->- params $ words "mem-offset code-offset code-size"- Just OpSha3 ->- params $ words "offset size"- Just OpCalldatacopy ->- params $ words "to from size"- Just OpExtcodecopy ->- params $ words "account mem-offset code-offset code-size"- Just OpReturn ->- params $ words "offset size"- Just OpJumpi ->- params $ words "destination condition"- _ -> mempty- where- params xs =- if length (vm ^. state . stack) >= length xs- then Map.fromList (zip xs (vm ^. state . stack))- else mempty--readOp :: Word8 -> ByteString -> Op-readOp x _ | x >= 0x80 && x <= 0x8f = OpDup (x - 0x80 + 1)-readOp x _ | x >= 0x90 && x <= 0x9f = OpSwap (x - 0x90 + 1)-readOp x _ | x >= 0xa0 && x <= 0xa4 = OpLog (x - 0xa0)-readOp x xs | x >= 0x60 && x <= 0x7f =- let n = x - 0x60 + 1- xs' = BS.take (num n) xs- in OpPush (word xs')-readOp x _ = case x of- 0x00 -> OpStop- 0x01 -> OpAdd- 0x02 -> OpMul- 0x03 -> OpSub- 0x04 -> OpDiv- 0x05 -> OpSdiv- 0x06 -> OpMod- 0x07 -> OpSmod- 0x08 -> OpAddmod- 0x09 -> OpMulmod- 0x0a -> OpExp- 0x0b -> OpSignextend- 0x10 -> OpLt- 0x11 -> OpGt- 0x12 -> OpSlt- 0x13 -> OpSgt- 0x14 -> OpEq- 0x15 -> OpIszero- 0x16 -> OpAnd- 0x17 -> OpOr- 0x18 -> OpXor- 0x19 -> OpNot- 0x1a -> OpByte- 0x20 -> OpSha3- 0x30 -> OpAddress- 0x31 -> OpBalance- 0x32 -> OpOrigin- 0x33 -> OpCaller- 0x34 -> OpCallvalue- 0x35 -> OpCalldataload- 0x36 -> OpCalldatasize- 0x37 -> OpCalldatacopy- 0x38 -> OpCodesize- 0x39 -> OpCodecopy- 0x3a -> OpGasprice- 0x3b -> OpExtcodesize- 0x3c -> OpExtcodecopy- 0x3d -> OpReturndatasize- 0x3e -> OpReturndatacopy- 0x40 -> OpBlockhash- 0x41 -> OpCoinbase- 0x42 -> OpTimestamp- 0x43 -> OpNumber- 0x44 -> OpDifficulty- 0x45 -> OpGaslimit- 0x50 -> OpPop- 0x51 -> OpMload- 0x52 -> OpMstore- 0x53 -> OpMstore8- 0x54 -> OpSload- 0x55 -> OpSstore- 0x56 -> OpJump- 0x57 -> OpJumpi- 0x58 -> OpPc- 0x59 -> OpMsize- 0x5a -> OpGas- 0x5b -> OpJumpdest- 0xf0 -> OpCreate- 0xf1 -> OpCall- 0xf2 -> OpCallcode- 0xf3 -> OpReturn- 0xf4 -> OpDelegatecall- 0xfd -> OpRevert- 0xfa -> OpStaticcall- 0xff -> OpSelfdestruct- _ -> (OpUnknown x)--mkCodeOps :: ByteString -> RegularVector.Vector (Int, Op)-mkCodeOps bytes = RegularVector.fromList . toList $ go 0 bytes- where- go !i !xs =- case BS.uncons xs of- Nothing ->- mempty- Just (x, xs') ->- let j = opSize x- in (i, readOp x xs') Seq.<| go (i + j) (BS.drop j xs)---- * Gas cost calculation helpers---- Gas cost function for CALL, transliterated from the Yellow Paper.-costOfCall- :: FeeSchedule Word- -> Maybe a -> Word -> Word -> Word- -> (Word, Word)-costOfCall (FeeSchedule {..}) recipient xValue availableGas xGas =- (c_gascap + c_extra, c_callgas)- where- c_extra =- num g_call + c_xfer + c_new- c_xfer =- if xValue /= 0 then num g_callvalue else 0- c_new =- if isNothing recipient then num g_newaccount else 0- c_callgas =- if xValue /= 0 then c_gascap + num g_callstipend else c_gascap- c_gascap =- if availableGas >= c_extra- then min xGas (allButOne64th (availableGas - c_extra))- else xGas--memoryCost :: FeeSchedule Word -> Word -> Word-memoryCost FeeSchedule{..} byteCount =- let- wordCount = ceilDiv byteCount 32- linearCost = g_memory * wordCount- quadraticCost = div (wordCount * wordCount) 512- in- if byteCount > exponentiate 2 32- then maxBound- else linearCost + quadraticCost+{-# Language DataKinds #-}+{-# Language GADTs #-}+{-# Language RecordWildCards #-}+{-# Language ScopedTypeVariables #-}+{-# Language StandaloneDeriving #-}+{-# Language StrictData #-}+{-# Language TemplateHaskell #-}+{-# Language TypeOperators #-}+{-# Language ViewPatterns #-}++module EVM where++import Prelude hiding (log, Word, exponent)++import Data.SBV hiding (Word, output, Unknown)+import Data.Proxy (Proxy(..))+import EVM.ABI+import EVM.Types+import EVM.Solidity+import EVM.Keccak+import EVM.Concrete (Word(..), w256, createAddress, wordValue, keccakBlob, create2Address)+import EVM.Symbolic+import EVM.Op+import EVM.FeeSchedule (FeeSchedule (..))+import Options.Generic as Options+import qualified EVM.Precompiled++import Data.Binary.Get (runGetOrFail)+import Data.Text (Text)+import Data.Word (Word8, Word32)+import Control.Lens hiding (op, (:<), (|>), (.>))+import Control.Monad.State.Strict hiding (state)++import Data.ByteString (ByteString)+import Data.ByteString.Lazy (fromStrict)+import Data.Map.Strict (Map)+import Data.Maybe (fromMaybe)+import Data.Semigroup (Semigroup (..))+import Data.Sequence (Seq)+import Data.Vector.Storable (Vector)+import Data.Foldable (toList)++import Data.Tree++import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LS+import qualified Data.ByteString.Char8 as Char8+import qualified Data.ByteArray as BA+import qualified Data.Map.Strict as Map+import qualified Data.Sequence as Seq+import qualified Data.Tree.Zipper as Zipper+import qualified Data.Vector.Storable as Vector+import qualified Data.Vector.Storable.Mutable as Vector++import qualified Data.Vector as RegularVector++import Crypto.Number.ModArithmetic (expFast)+import Crypto.Hash (Digest, SHA256, RIPEMD160)+import qualified Crypto.Hash as Crypto++-- * Data types++-- | EVM failure modes+data Error+ = BalanceTooLow Word Word+ | UnrecognizedOpcode Word8+ | SelfDestruction+ | StackUnderrun+ | BadJumpDestination+ | Revert ByteString+ | NoSuchContract Addr+ | OutOfGas Word Word+ | BadCheatCode (Maybe Word32)+ | StackLimitExceeded+ | IllegalOverflow+ | Query Query+ | Choose Choose+ | StateChangeWhileStatic+ | InvalidMemoryAccess+ | CallDepthLimitReached+ | MaxCodeSizeExceeded Word Word+ | PrecompileFailure+ | UnexpectedSymbolicArg+ | DeadPath+deriving instance Show Error++-- | The possible result states of a VM+data VMResult+ = VMFailure Error -- ^ An operation failed+ | VMSuccess Buffer -- ^ Reached STOP, RETURN, or end-of-code++deriving instance Show VMResult++-- | The state of a stepwise EVM execution+data VM = VM+ { _result :: Maybe VMResult+ , _state :: FrameState+ , _frames :: [Frame]+ , _env :: Env+ , _block :: Block+ , _tx :: TxState+ , _logs :: Seq Log+ , _traces :: Zipper.TreePos Zipper.Empty Trace+ , _cache :: Cache+ , _burned :: Word+ , _pathConditions :: [SBool]+ , _iterations :: Map CodeLocation Int+ }++data Trace = Trace+ { _traceCodehash :: W256+ , _traceOpIx :: Int+ , _traceData :: TraceData+ }++data TraceData+ = EventTrace Log+ | FrameTrace FrameContext+ | QueryTrace Query+ | ErrorTrace Error+ | EntryTrace Text+ | ReturnTrace Buffer FrameContext++-- | Queries halt execution until resolved through RPC calls or SMT queries+data Query where+ PleaseFetchContract :: Addr -> (Contract -> EVM ()) -> Query+ PleaseFetchSlot :: Addr -> Word -> (Word -> EVM ()) -> Query+ PleaseAskSMT :: SymWord -> [SBool] -> (JumpCondition -> EVM ()) -> Query++data Choose where+ PleaseChoosePath :: (Bool -> EVM ()) -> Choose++instance Show Query where+ showsPrec _ = \case+ PleaseFetchContract addr _ ->+ (("<EVM.Query: fetch contract " ++ show addr ++ ">") ++)+ PleaseFetchSlot addr slot _ ->+ (("<EVM.Query: fetch slot "+ ++ show slot ++ " for "+ ++ show addr ++ ">") ++)+ PleaseAskSMT condition pathConditions _ ->+ (("<EVM.Query: ask SMT about "+ ++ show condition ++ " in context "+ ++ show pathConditions ++ ">") ++)++instance Show Choose where+ showsPrec _ = \case+ PleaseChoosePath _ ->+ (("<EVM.Choice: waiting for user to select path (0,1)") ++)++-- | Alias for the type of e.g. @exec1@.+type EVM a = State VM a++type CodeLocation = (Addr, Int)++-- | The possible return values of a SMT query regarding JUMPI+data JumpCondition = Iszero Bool | Unknown | Inconsistent+ deriving (Show)++-- | The cache is data that can be persisted for efficiency:+-- any expensive query that is constant at least within a block.+data Cache = Cache+ { _fetched :: Map Addr Contract,+ _path :: Map (CodeLocation, Int) Bool+ } deriving Show++-- | A way to specify an initial VM state+data VMOpts = VMOpts+ { vmoptContract :: Contract+ , vmoptCalldata :: (Buffer, (SWord 32)) -- maximum size of uint32 as per eip 1985+ , vmoptValue :: SymWord+ , vmoptAddress :: Addr+ , vmoptCaller :: SAddr+ , vmoptOrigin :: Addr+ , vmoptGas :: W256+ , vmoptGaslimit :: W256+ , vmoptNumber :: W256+ , vmoptTimestamp :: W256+ , vmoptCoinbase :: Addr+ , vmoptDifficulty :: W256+ , vmoptMaxCodeSize :: W256+ , vmoptBlockGaslimit :: W256+ , vmoptGasprice :: W256+ , vmoptSchedule :: FeeSchedule Word+ , vmoptChainId :: W256+ , vmoptCreate :: Bool+ , vmoptStorageModel :: StorageModel+ } deriving Show++-- | A log entry+data Log = Log Addr Buffer [SymWord]++-- | An entry in the VM's "call/create stack"+data Frame = Frame+ { _frameContext :: FrameContext+ , _frameState :: FrameState+ }++-- | Call/create info+data FrameContext+ = CreationContext+ { creationContextCodehash :: W256+ , creationContextReversion :: Map Addr Contract+ , creationContextSubstate :: SubState+ }+ | CallContext+ { callContextOffset :: Word+ , callContextSize :: Word+ , callContextCodehash :: W256+ , callContextAbi :: Maybe Word+ , callContextData :: Buffer+ , callContextReversion :: Map Addr Contract+ , callContextSubState :: SubState+ }++-- | The "registers" of the VM along with memory and data stack+data FrameState = FrameState+ { _contract :: Addr+ , _codeContract :: Addr+ , _code :: ByteString+ , _pc :: Int+ , _stack :: [SymWord]+ , _memory :: Buffer+ , _memorySize :: Int+ , _calldata :: (Buffer, (SWord 32))+ , _callvalue :: SymWord+ , _caller :: SAddr+ , _gas :: Word+ , _returndata :: Buffer+ , _static :: Bool+ }++-- | The state that spans a whole transaction+data TxState = TxState+ { _gasprice :: Word+ , _txgaslimit :: Word+ , _origin :: Addr+ , _toAddr :: Addr+ , _value :: SymWord+ , _substate :: SubState+ , _isCreate :: Bool+ , _txReversion :: Map Addr Contract+ }++-- | The "accrued substate" across a transaction+data SubState = SubState+ { _selfdestructs :: [Addr]+ , _touchedAccounts :: [Addr]+ , _refunds :: [(Addr, Word)]+ -- in principle we should include logs here, but do not for now+ }++-- | A contract is either in creation (running its "constructor") or+-- post-creation, and code in these two modes is treated differently+-- by instructions like @EXTCODEHASH@, so we distinguish these two+-- code types.+data ContractCode+ = InitCode ByteString -- ^ "Constructor" code, during contract creation+ | RuntimeCode ByteString -- ^ "Instance" code, after contract creation+ deriving (Show, Eq)++-- | A contract can either have concrete or symbolic storage+-- depending on what type of execution we are doing+data Storage+ = Concrete (Map Word SymWord)+ | Symbolic (SArray (WordN 256) (WordN 256))+ deriving (Show)++-- to allow for Eq Contract (which useful for debugging vmtests)+-- we mock an instance of Eq for symbolic storage.+-- It should not (cannot) be used though.+instance Eq Storage where+ (==) (Concrete a) (Concrete b) = fmap forceLit a == fmap forceLit b+ (==) (Symbolic _) (Concrete _) = False+ (==) (Concrete _) (Symbolic _) = False+ (==) _ _ = error "do not compare two symbolic arrays like this!"++-- | The state of a contract+data Contract = Contract+ { _contractcode :: ContractCode+ , _storage :: Storage+ , _balance :: Word+ , _nonce :: Word+ , _codehash :: W256+ , _opIxMap :: Vector Int+ , _codeOps :: RegularVector.Vector (Int, Op)+ , _external :: Bool+ , _origStorage :: Map Word Word+ }++deriving instance Show Contract+deriving instance Eq Contract++-- | When doing symbolic execution, we have three different+-- ways to model the storage of contracts. This determines+-- not only the initial contract storage model but also how+-- RPC or state fetched contracts will be modeled.+data StorageModel+ = ConcreteS -- ^ Uses `Concrete` Storage. Reading / Writing from abstract+ -- locations causes a runtime failure. Can be nicely combined with RPC.++ | SymbolicS -- ^ Uses `Symbolic` Storage. Reading / Writing never reaches RPC,+ -- but always done using an SMT array with no default value.++ | InitialS -- ^ Uses `Symbolic` Storage. Reading / Writing never reaches RPC,+ -- but always done using an SMT array with 0 as the default value.++ deriving (Read, Show)++instance ParseField StorageModel++-- | Various environmental data+data Env = Env+ { _contracts :: Map Addr Contract+ , _chainId :: Word+ , _storageModel :: StorageModel+ , _sha3Crack :: Map Word ByteString+ , _keccakUsed :: [([SWord 8], SWord 256)]+ }+++-- | Data about the block+data Block = Block+ { _coinbase :: Addr+ , _timestamp :: Word+ , _number :: Word+ , _difficulty :: Word+ , _gaslimit :: Word+ , _maxCodeSize :: Word+ , _schedule :: FeeSchedule Word+ }++blankState :: FrameState+blankState = FrameState+ { _contract = 0+ , _codeContract = 0+ , _code = mempty+ , _pc = 0+ , _stack = mempty+ , _memory = mempty+ , _memorySize = 0+ , _calldata = (mempty, 0)+ , _callvalue = 0+ , _caller = 0+ , _gas = 0+ , _returndata = mempty+ , _static = False+ }++makeLenses ''FrameState+makeLenses ''Frame+makeLenses ''Block+makeLenses ''TxState+makeLenses ''SubState+makeLenses ''Contract+makeLenses ''Env+makeLenses ''Cache+makeLenses ''Trace+makeLenses ''VM++-- | An "external" view of a contract's bytecode, appropriate for+-- e.g. @EXTCODEHASH@.+bytecode :: Getter Contract ByteString+bytecode = contractcode . to f+ where f (InitCode _) = BS.empty+ f (RuntimeCode b) = b++instance Semigroup Cache where+ a <> b = Cache+ { _fetched = mappend (view fetched a) (view fetched b),+ _path = mappend (view path a) (view path b)+ }++instance Monoid Cache where+ mempty = Cache { _fetched = mempty,+ _path = mempty+ }++-- * Data accessors++currentContract :: VM -> Maybe Contract+currentContract vm =+ view (env . contracts . at (view (state . codeContract) vm)) vm++-- * Data constructors++makeVm :: VMOpts -> VM+makeVm o = VM+ { _result = Nothing+ , _frames = mempty+ , _tx = TxState+ { _gasprice = w256 $ vmoptGasprice o+ , _txgaslimit = w256 $ vmoptGaslimit o+ , _origin = vmoptOrigin o+ , _toAddr = vmoptAddress o+ , _value = vmoptValue o+ , _substate = SubState mempty mempty mempty+ , _isCreate = vmoptCreate o+ , _txReversion = Map.fromList+ [(vmoptAddress o, vmoptContract o)]+ }+ , _logs = mempty+ , _traces = Zipper.fromForest []+ , _block = Block+ { _coinbase = vmoptCoinbase o+ , _timestamp = w256 $ vmoptTimestamp o+ , _number = w256 $ vmoptNumber o+ , _difficulty = w256 $ vmoptDifficulty o+ , _maxCodeSize = w256 $ vmoptMaxCodeSize o+ , _gaslimit = w256 $ vmoptBlockGaslimit o+ , _schedule = vmoptSchedule o+ }+ , _state = FrameState+ { _pc = 0+ , _stack = mempty+ , _memory = mempty+ , _memorySize = 0+ , _code = theCode+ , _contract = vmoptAddress o+ , _codeContract = vmoptAddress o+ , _calldata = vmoptCalldata o+ , _callvalue = vmoptValue o+ , _caller = vmoptCaller o+ , _gas = w256 $ vmoptGas o+ , _returndata = mempty+ , _static = False+ }+ , _env = Env+ { _sha3Crack = mempty+ , _chainId = w256 $ vmoptChainId o+ , _contracts = Map.fromList+ [(vmoptAddress o, vmoptContract o)]+ , _keccakUsed = mempty+ , _storageModel = vmoptStorageModel o+ }+ , _cache = Cache (Map.fromList+ [(vmoptAddress o, vmoptContract o)])+ mempty+ , _burned = 0+ , _pathConditions = []+ , _iterations = mempty+ } where theCode = case _contractcode (vmoptContract o) of+ InitCode b -> b+ RuntimeCode b -> b++-- | Initialize empty contract with given code+initialContract :: ContractCode -> Contract+initialContract theContractCode = Contract+ { _contractcode = theContractCode+ , _codehash =+ if BS.null theCode then 0 else+ keccak (stripBytecodeMetadata theCode)+ , _storage = Concrete mempty+ , _balance = 0+ , _nonce = 0+ , _opIxMap = mkOpIxMap theCode+ , _codeOps = mkCodeOps theCode+ , _external = False+ , _origStorage = mempty+ } where theCode = case theContractCode of+ InitCode b -> b+ RuntimeCode b -> b++contractWithStore :: ContractCode -> Storage -> Contract+contractWithStore theContractCode store =+ initialContract theContractCode & set storage store++-- * Opcode dispatch (exec1)++-- | Update program counter+next :: (?op :: Word8) => EVM ()+next = modifying (state . pc) (+ (opSize ?op))++-- | Executes the EVM one step+exec1 :: EVM ()+exec1 = do+ vm <- get++ let+ -- Convenience function to access parts of the current VM state.+ -- Arcane type signature needed to avoid monomorphism restriction.+ the :: (b -> VM -> Const a VM) -> ((a -> Const a a) -> b) -> a+ the f g = view (f . g) vm++ -- Convenient aliases+ mem = the state memory+ stk = the state stack+ self = the state contract+ this = fromMaybe (error "internal error: state contract") (preview (ix self) (the env contracts))++ fees@FeeSchedule {..} = the block schedule++ doStop = finishFrame (FrameReturned mempty)++ if self > 0x0 && self <= 0x9 then do+ -- call to precompile+ let ?op = 0x00 -- dummy value+ let+ calldatasize = snd (the state calldata)+ case unliteral calldatasize of+ Nothing -> vmError UnexpectedSymbolicArg+ Just calldatasize' -> do+ copyBytesToMemory (fst $ the state calldata) (num calldatasize') 0 0+ executePrecompile self (the state gas) 0 (num calldatasize') 0 0 []+ vmx <- get+ case view (state.stack) vmx of+ (x:_) -> case maybeLitWord x of+ Just 0 -> do+ fetchAccount self $ \_ -> do+ touchAccount self+ vmError PrecompileFailure+ Just _ ->+ fetchAccount self $ \_ -> do+ touchAccount self+ out <- use (state . returndata)+ finishFrame (FrameReturned out)+ Nothing -> vmError UnexpectedSymbolicArg+ _ ->+ underrun++ else if the state pc >= num (BS.length (the state code))+ then doStop++ else do+ let ?op = BS.index (the state code) (the state pc)++ case ?op of++ -- op: PUSH+ x | x >= 0x60 && x <= 0x7f -> do+ let !n = num x - 0x60 + 1+ !xs = BS.take n (BS.drop (1 + the state pc)+ (the state code))+ limitStack 1 $+ burn g_verylow $ do+ next+ push (w256 (word xs))++ -- op: DUP+ x | x >= 0x80 && x <= 0x8f -> do+ let !i = x - 0x80 + 1+ case preview (ix (num i - 1)) stk of+ Nothing -> underrun+ Just y ->+ limitStack 1 $+ burn g_verylow $ do+ next+ pushSym y++ -- op: SWAP+ x | x >= 0x90 && x <= 0x9f -> do+ let i = num (x - 0x90 + 1)+ if length stk < i + 1+ then underrun+ else+ burn g_verylow $ do+ next+ zoom (state . stack) $ do+ assign (ix 0) (stk ^?! ix i)+ assign (ix i) (stk ^?! ix 0)++ -- op: LOG+ x | x >= 0xa0 && x <= 0xa4 ->+ notStatic $+ let n = (num x - 0xa0) in+ case stk of+ (xOffset':xSize':xs) ->+ if length xs < n+ then underrun+ else+ forceConcrete2 (xOffset', xSize') $ \(xOffset, xSize) -> do+ let (topics, xs') = splitAt n xs+ bytes = readMemory (num xOffset) (num xSize) vm+ log = Log self bytes topics++ burn (g_log + g_logdata * xSize + num n * g_logtopic) $+ accessMemoryRange fees xOffset xSize $ do+ traceLog log+ next+ assign (state . stack) xs'+ pushToSequence logs log+ _ ->+ underrun++ -- op: STOP+ 0x00 -> doStop++ -- op: ADD+ 0x01 -> stackOp2 (const g_verylow) (uncurry (+))+ -- op: MUL+ 0x02 -> stackOp2 (const g_low) (uncurry (*))+ -- op: SUB+ 0x03 -> stackOp2 (const g_verylow) (uncurry (-))++ -- op: DIV+ 0x04 -> stackOp2 (const g_low) (uncurry (sDiv))++ -- op: SDIV+ 0x05 ->+ stackOp2 (const g_low) (uncurry sdiv)++ -- op: MOD+ 0x06 -> stackOp2 (const g_low) $ \(x, y) -> ite (y .== 0) 0 (x `sMod` y)++ -- op: SMOD+ 0x07 -> stackOp2 (const g_low) $ uncurry smod+ -- op: ADDMOD+ 0x08 -> stackOp3 (const g_mid) (\(x, y, z) -> addmod x y z)+ -- op: MULMOD+ 0x09 -> stackOp3 (const g_mid) (\(x, y, z) -> mulmod x y z)++ -- op: LT+ 0x10 -> stackOp2 (const g_verylow) $ \(x, y) -> ite (x .< y) 1 0+ -- op: GT+ 0x11 -> stackOp2 (const g_verylow) $ \(x, y) -> ite (x .> y) 1 0+ -- op: SLT+ 0x12 -> stackOp2 (const g_verylow) $ uncurry slt+ -- op: SGT+ 0x13 -> stackOp2 (const g_verylow) $ uncurry sgt++ -- op: EQ+ 0x14 -> stackOp2 (const g_verylow) $ \(x, y) -> ite (x .== y) 1 0+ -- op: ISZERO+ 0x15 -> stackOp1 (const g_verylow) $ \x -> ite (x .== 0) 1 0++ -- op: AND+ 0x16 -> stackOp2 (const g_verylow) $ uncurry (.&.)+ -- op: OR+ 0x17 -> stackOp2 (const g_verylow) $ uncurry (.|.)+ -- op: XOR+ 0x18 -> stackOp2 (const g_verylow) $ uncurry xor+ -- op: NOT+ 0x19 -> stackOp1 (const g_verylow) complement++ -- op: BYTE+ 0x1a -> stackOp2 (const g_verylow) $ \case+ (n, _) | (forceLit n) >= 32 -> 0+ (n, x) | otherwise -> 0xff .&. shiftR x (8 * (31 - num (forceLit n)))++ -- op: SHL+ 0x1b -> stackOp2 (const g_verylow) $ \((S _ n), (S _ x)) -> sw256 $ sShiftLeft x n+ -- op: SHR+ 0x1c -> stackOp2 (const g_verylow) $ \((S _ n), (S _ x)) -> sw256 $ sShiftRight x n+ -- op: SAR+ 0x1d -> stackOp2 (const g_verylow) $ \((S _ n), (S _ x)) -> sw256 $ sSignedShiftArithRight x n++ -- op: SHA3+ -- more accurately refered to as KECCAK+ 0x20 ->+ case stk of+ (xOffset' : xSize' : xs) ->+ forceConcrete xOffset' $+ \xOffset -> forceConcrete xSize' $ \xSize -> do+ (hash, invMap) <- case readMemory xOffset xSize vm of+ ConcreteBuffer bs -> pure (litWord $ keccakBlob bs, Map.singleton (keccakBlob bs) bs)++ -- Although we would like to simply assert that the uninterpreted function symkeccak'+ -- is injective, this proves to cause a lot of concern for our smt solvers, probably+ -- due to the introduction of universal quantifiers into the queries.++ -- Instead, we keep track of all of the particular invocations of symkeccak' we see+ -- (similarly to sha3Crack), and simply assert that injectivity holds for these+ -- particular invocations.++ SymbolicBuffer bs -> do+ let hash' = symkeccak' bs+ previousUsed = view (env . keccakUsed) vm+ env . keccakUsed <>= [(bs, hash')]+ pathConditions <>= fmap (\(preimage, image) ->+ image .== hash' .=> preimage .== bs)+ previousUsed+ return (sw256 hash', mempty)++ burn (g_sha3 + g_sha3word * ceilDiv (num xSize) 32) $+ accessMemoryRange fees xOffset xSize $ do+ next+ assign (state . stack) (hash : xs)+ (env . sha3Crack) <>= invMap+ _ -> underrun++ -- op: ADDRESS+ 0x30 ->+ limitStack 1 $+ burn g_base (next >> push (num self))++ -- op: BALANCE+ 0x31 ->+ case stk of+ (x':xs) -> forceConcrete x' $ \x ->+ burn g_balance $+ fetchAccount (num x) $ \c -> do+ next+ assign (state . stack) xs+ push (view balance c)+ [] ->+ underrun++ -- op: ORIGIN+ 0x32 ->+ limitStack 1 . burn g_base $+ next >> push (num (the tx origin))++ -- op: CALLER+ 0x33 ->+ limitStack 1 . burn g_base $+ let toSymWord = sw256 . sFromIntegral . saddressWord160+ in next >> pushSym (toSymWord (the state caller))++ -- op: CALLVALUE+ 0x34 ->+ limitStack 1 . burn g_base $+ next >> pushSym (the state callvalue)++ -- op: CALLDATALOAD+ 0x35 -> stackOp1 (const g_verylow) $+ \(S _ x) -> uncurry (readSWordWithBound (sFromIntegral x)) (the state calldata)++ -- op: CALLDATASIZE+ 0x36 ->+ limitStack 1 . burn g_base $+ next >> pushSym (sw256 . zeroExtend . snd $ (the state calldata))++ -- op: CALLDATACOPY+ 0x37 ->+ case stk of+ (xTo' : xFrom' : xSize' : xs) -> forceConcrete3 (xTo',xFrom',xSize') $ \(xTo,xFrom,xSize) ->+ burn (g_verylow + g_copy * ceilDiv xSize 32) $+ accessUnboundedMemoryRange fees xTo xSize $ do+ next+ assign (state . stack) xs+ case the state calldata of+ (SymbolicBuffer cd, cdlen) -> copyBytesToMemory (SymbolicBuffer [ite (i .<= cdlen) x 0 | (x, i) <- zip cd [1..]]) xSize xFrom xTo+ -- when calldata is concrete,+ -- the bound should always be equal to the bytestring length+ (cd, _) -> copyBytesToMemory cd xSize xFrom xTo+ _ -> underrun++ -- op: CODESIZE+ 0x38 ->+ limitStack 1 . burn g_base $+ next >> push (num (BS.length (the state code)))++ -- op: CODECOPY+ 0x39 ->+ case stk of+ (memOffset' : codeOffset' : n' : xs) -> forceConcrete3 (memOffset',codeOffset',n') $ \(memOffset,codeOffset,n) -> do+ burn (g_verylow + g_copy * ceilDiv (num n) 32) $+ accessUnboundedMemoryRange fees memOffset n $ do+ next+ assign (state . stack) xs+ copyBytesToMemory (ConcreteBuffer (the state code))+ n codeOffset memOffset+ _ -> underrun++ -- op: GASPRICE+ 0x3a ->+ limitStack 1 . burn g_base $+ next >> push (the tx gasprice)++ -- op: EXTCODESIZE+ 0x3b ->+ case stk of+ (x':xs) -> forceConcrete x' $ \x ->+ if x == num cheatCode+ then do+ next+ assign (state . stack) xs+ push (w256 1)+ else+ burn g_extcode $+ fetchAccount (num x) $ \c -> do+ next+ assign (state . stack) xs+ push (num (BS.length (view bytecode c)))+ [] ->+ underrun++ -- op: EXTCODECOPY+ 0x3c ->+ case stk of+ ( extAccount'+ : memOffset'+ : codeOffset'+ : codeSize'+ : xs ) ->+ forceConcrete4 (extAccount', memOffset', codeOffset', codeSize') $+ \(extAccount, memOffset, codeOffset, codeSize) ->+ burn (g_extcode + g_copy * ceilDiv (num codeSize) 32) $+ accessUnboundedMemoryRange fees memOffset codeSize $+ fetchAccount (num extAccount) $ \c -> do+ next+ assign (state . stack) xs+ copyBytesToMemory (ConcreteBuffer (view bytecode c))+ codeSize codeOffset memOffset+ _ -> underrun++ -- op: RETURNDATASIZE+ 0x3d ->+ limitStack 1 . burn g_base $+ next >> push (num $ len (the state returndata))++ -- op: RETURNDATACOPY+ 0x3e ->+ case stk of+ (xTo' : xFrom' : xSize' :xs) -> forceConcrete3 (xTo', xFrom', xSize') $+ \(xTo, xFrom, xSize) ->+ burn (g_verylow + g_copy * ceilDiv xSize 32) $+ accessUnboundedMemoryRange fees xTo xSize $ do+ next+ assign (state . stack) xs+ if len (the state returndata) < num xFrom + num xSize+ then vmError InvalidMemoryAccess+ else copyBytesToMemory (the state returndata) xSize xFrom xTo+ _ -> underrun++ -- op: EXTCODEHASH+ 0x3f ->+ case stk of+ (x':xs) -> forceConcrete x' $ \x ->+ burn g_extcodehash $ do+ next+ assign (state . stack) xs+ fetchAccount (num x) $ \c ->+ if accountEmpty c+ then push (num (0 :: Int))+ else push (num (keccak (view bytecode c)))+ [] ->+ underrun++ -- op: BLOCKHASH+ 0x40 -> do+ -- We adopt the fake block hash scheme of the VMTests,+ -- so that blockhash(i) is the hash of i as decimal ASCII.+ stackOp1 (const g_blockhash) $+ \(forceLit -> i) ->+ if i + 256 < the block number || i >= the block number+ then 0+ else+ (num i :: Integer)+ & show & Char8.pack & keccak & num++ -- op: COINBASE+ 0x41 ->+ limitStack 1 . burn g_base $+ next >> push (num (the block coinbase))++ -- op: TIMESTAMP+ 0x42 ->+ limitStack 1 . burn g_base $+ next >> push (the block timestamp)++ -- op: NUMBER+ 0x43 ->+ limitStack 1 . burn g_base $+ next >> push (the block number)++ -- op: DIFFICULTY+ 0x44 ->+ limitStack 1 . burn g_base $+ next >> push (the block difficulty)++ -- op: GASLIMIT+ 0x45 ->+ limitStack 1 . burn g_base $+ next >> push (the block gaslimit)++ -- op: CHAINID+ 0x46 ->+ limitStack 1 . burn g_base $+ next >> push (the env chainId)++ -- op: SELFBALANCE+ 0x47 ->+ limitStack 1 . burn g_low $+ next >> push (view balance this)++ -- op: POP+ 0x50 ->+ case stk of+ (_:xs) -> burn g_base (next >> assign (state . stack) xs)+ _ -> underrun++ -- op: MLOAD+ 0x51 ->+ case stk of+ (x':xs) -> forceConcrete x' $ \x ->+ burn g_verylow $+ accessMemoryWord fees x $ do+ next+ assign (state . stack) (view (word256At (num x)) mem : xs)+ _ -> underrun++ -- op: MSTORE+ 0x52 ->+ case stk of+ (x':y:xs) -> forceConcrete x' $ \x ->+ burn g_verylow $+ accessMemoryWord fees x $ do+ next+ assign (state . memory . word256At (num x)) y+ assign (state . stack) xs+ _ -> underrun++ -- op: MSTORE8+ 0x53 ->+ case stk of+ (x':(S _ y):xs) -> forceConcrete x' $ \x ->+ burn g_verylow $+ accessMemoryRange fees x 1 $ do+ let yByte = bvExtract (Proxy :: Proxy 7) (Proxy :: Proxy 0) y+ next+ modifying (state . memory) (setMemoryByte x yByte)+ assign (state . stack) xs+ _ -> underrun++ -- op: SLOAD+ 0x54 ->+ case stk of+ (x:xs) ->+ burn g_sload $+ accessStorage self x $ \y -> do+ next+ assign (state . stack) (y:xs)+ _ -> underrun++ -- op: SSTORE+ 0x55 ->+ notStatic $+ case stk of+ (x:new:xs) ->+ accessStorage self x $ \current -> do+ availableGas <- use (state . gas)++ if availableGas <= g_callstipend+ then finishFrame (FrameErrored (OutOfGas availableGas g_callstipend))+ else do+ let original = case view storage this of+ Concrete _ -> fromMaybe 0 (Map.lookup (forceLit x) (view origStorage this))+ Symbolic _ -> 0 -- we don't use this value anywhere anyway+ cost = case (maybeLitWord current, maybeLitWord new) of+ (Just current', Just new') ->+ if (current' == new') then g_sload+ else if (current' == original) && (original == 0) then g_sset+ else if (current' == original) then g_sreset+ else g_sload++ -- if any of the arguments are symbolic,+ -- assume worst case scenario+ _ -> g_sset++ burn cost $ do+ next+ assign (state . stack) xs+ modifying (env . contracts . ix self . storage)+ (writeStorage x new)++ case (maybeLitWord current, maybeLitWord new) of+ (Just current', Just new') ->+ unless (current' == new') $+ if current' == original+ then when (original /= 0 && new' == 0) $+ refund r_sclear+ else do+ when (original /= 0) $+ if new' == 0+ then refund r_sclear+ else unRefund r_sclear+ when (original == new') $+ if original == 0+ then refund (g_sset - g_sload)+ else refund (g_sreset - g_sload)+ -- if any of the arguments are symbolic,+ -- don't change the refund counter+ _ -> noop+ _ -> underrun++ -- op: JUMP+ 0x56 ->+ case stk of+ (x:xs) ->+ burn g_mid $ forceConcrete x $ \x' ->+ checkJump x' xs+ _ -> underrun++ -- op: JUMPI+ 0x57 -> do+ case stk of+ (x:y:xs) -> forceConcrete x $ \x' ->+ burn g_high $+ let jump :: Bool -> EVM ()+ jump True = assign (state . stack) xs >> next+ jump _ = checkJump x' xs+ in case maybeLitWord y of+ Just y' -> jump (0 == y')+ -- if the jump condition is symbolic, an smt query has to be made.+ Nothing -> askSMT (self, the state pc) y jump+ _ -> underrun++ -- op: PC+ 0x58 ->+ limitStack 1 . burn g_base $+ next >> push (num (the state pc))++ -- op: MSIZE+ 0x59 ->+ limitStack 1 . burn g_base $+ next >> push (num (the state memorySize))++ -- op: GAS+ 0x5a ->+ limitStack 1 . burn g_base $+ next >> push (the state gas - g_base)++ -- op: JUMPDEST+ 0x5b -> burn g_jumpdest next++ -- op: EXP+ 0x0a ->+ let cost (_ ,(forceLit -> exponent)) =+ if exponent == 0+ then g_exp+ else g_exp + g_expbyte * num (ceilDiv (1 + log2 exponent) 8)+ in stackOp2 cost $ \((S _ x),(S _ y)) -> sw256 $ x .^ y++ -- op: SIGNEXTEND+ 0x0b ->+ stackOp2 (const g_low) $ \((forceLit -> bytes), w@(S _ x)) ->+ if bytes >= 32 then w+ else let n = num bytes * 8 + 7 in+ sw256 $ ite (sTestBit x n)+ (x .|. complement (bit n - 1))+ (x .&. (bit n - 1))++ -- op: CREATE+ 0xf0 ->+ notStatic $+ case stk of+ (xValue' : xOffset' : xSize' : xs) -> forceConcrete3 (xValue', xOffset', xSize') $+ \(xValue, xOffset, xSize) -> do+ accessMemoryRange fees xOffset xSize $ do+ availableGas <- use (state . gas)+ let+ newAddr = createAddress self (wordValue (view nonce this))+ (cost, gas') = costOfCreate fees availableGas 0+ burn (cost - gas') $ forceConcreteBuffer (readMemory (num xOffset) (num xSize) vm) $ \initCode ->+ create self this gas' xValue xs newAddr initCode+ _ -> underrun++ -- op: CALL+ 0xf1 ->+ case stk of+ ( xGas'+ : xTo'+ : (forceLit -> xValue)+ : xInOffset'+ : xInSize'+ : xOutOffset'+ : xOutSize'+ : xs+ ) -> forceConcrete6 (xGas', xTo', xInOffset', xInSize', xOutOffset', xOutSize') $+ \(xGas, (num -> xTo), xInOffset, xInSize, xOutOffset, xOutSize) ->+ (if xValue > 0 then notStatic else id) $+ case xTo of+ n | n > 0 && n <= 9 ->+ precompiledContract this xGas xTo xTo xValue xInOffset xInSize xOutOffset xOutSize xs+ n | num n == cheatCode ->+ do+ assign (state . stack) xs+ cheat (xInOffset, xInSize) (xOutOffset, xOutSize)+ _ -> delegateCall this xGas xTo xTo xValue xInOffset xInSize xOutOffset xOutSize xs $ do+ zoom state $ do+ assign callvalue (litWord xValue)+ assign caller (litAddr self)+ assign contract xTo+ zoom (env . contracts) $ do+ ix self . balance -= xValue+ ix xTo . balance += xValue+ touchAccount self+ touchAccount xTo+ _ ->+ underrun++ -- op: CALLCODE+ 0xf2 ->+ case stk of+ ( xGas'+ : xTo'+ : (forceLit -> xValue)+ : xInOffset'+ : xInSize'+ : xOutOffset'+ : xOutSize'+ : xs+ ) -> forceConcrete6 (xGas', xTo', xInOffset', xInSize', xOutOffset', xOutSize') $+ \(xGas, (num -> xTo), xInOffset, xInSize, xOutOffset, xOutSize) ->+ case xTo of+ n | n > 0 && n <= 9 ->+ precompiledContract this xGas xTo self xValue xInOffset xInSize xOutOffset xOutSize xs+ _ -> delegateCall this xGas xTo self xValue xInOffset xInSize xOutOffset xOutSize xs $ do+ zoom state $ do+ assign callvalue (litWord xValue)+ assign caller (litAddr self)+ touchAccount self+ _ ->+ underrun++ -- op: RETURN+ 0xf3 ->+ case stk of+ (xOffset' : xSize' :_) -> forceConcrete2 (xOffset', xSize') $ \(xOffset, xSize) ->+ accessMemoryRange fees xOffset xSize $ do+ let+ output = readMemory xOffset xSize vm+ codesize = num (len output)+ maxsize = the block maxCodeSize+ case view frames vm of+ [] ->+ case (the tx isCreate) of+ True ->+ if codesize > maxsize+ then+ finishFrame (FrameErrored (MaxCodeSizeExceeded maxsize codesize))+ else+ burn (g_codedeposit * codesize) $+ finishFrame (FrameReturned output)+ False ->+ finishFrame (FrameReturned output)+ (frame: _) -> do+ let+ context = view frameContext frame+ case context of+ CreationContext {} ->+ if codesize > maxsize+ then+ finishFrame (FrameErrored (MaxCodeSizeExceeded maxsize codesize))+ else+ burn (g_codedeposit * codesize) $+ finishFrame (FrameReturned output)+ CallContext {} ->+ finishFrame (FrameReturned output)+ _ -> underrun++ -- op: DELEGATECALL+ 0xf4 ->+ case stk of+ (xGas'+ :xTo'+ :xInOffset'+ :xInSize'+ :xOutOffset'+ :xOutSize'+ :xs) -> forceConcrete6 (xGas', xTo', xInOffset', xInSize', xOutOffset', xOutSize') $+ \(xGas, (num -> xTo), xInOffset, xInSize, xOutOffset, xOutSize) ->+ case xTo of+ n | n > 0 && n <= 9 ->+ precompiledContract this xGas xTo self 0 xInOffset xInSize xOutOffset xOutSize xs+ n | num n == cheatCode -> do+ assign (state . stack) xs+ cheat (xInOffset, xInSize) (xOutOffset, xOutSize)+ _ -> do+ delegateCall this xGas xTo self 0 xInOffset xInSize xOutOffset xOutSize xs $ do+ touchAccount self+ _ -> underrun++ -- op: CREATE2+ 0xf5 -> notStatic $+ case stk of+ (xValue'+ :xOffset'+ :xSize'+ :xSalt'+ :xs) -> forceConcrete4 (xValue', xOffset', xSize', xSalt') $+ \(xValue, xOffset, xSize, xSalt) ->+ accessMemoryRange fees xOffset xSize $ do+ availableGas <- use (state . gas)+ forceConcreteBuffer (readMemory (num xOffset) (num xSize) vm) $ \initCode ->+ let+ newAddr = create2Address self (num xSalt) initCode+ (cost, gas') = costOfCreate fees availableGas xSize+ in burn (cost - gas') $+ create self this gas' xValue xs newAddr initCode+ _ -> underrun++ -- op: STATICCALL+ 0xfa ->+ case stk of+ (xGas'+ :xTo'+ :xInOffset'+ :xInSize'+ :xOutOffset'+ :xOutSize'+ :xs) -> forceConcrete6 (xGas', xTo', xInOffset', xInSize', xOutOffset', xOutSize') $+ \(xGas, (num -> xTo), xInOffset, xInSize, xOutOffset, xOutSize) ->+ case xTo of+ n | n > 0 && n <= 9 ->+ precompiledContract this xGas xTo xTo 0 xInOffset xInSize xOutOffset xOutSize xs+ _ -> delegateCall this xGas xTo xTo 0 xInOffset xInSize xOutOffset xOutSize xs $ do+ zoom state $ do+ assign callvalue 0+ assign caller (litAddr self)+ assign contract xTo+ assign static True+ touchAccount self+ touchAccount xTo+ _ ->+ underrun++ -- op: SELFDESTRUCT+ 0xff ->+ notStatic $+ case stk of+ [] -> underrun+ (xTo':_) -> forceConcrete xTo' $ \(num -> xTo) ->+ let+ funds = view balance this+ recipientExists = accountExists xTo vm+ c_new = if not recipientExists && funds /= 0+ then num g_selfdestruct_newaccount+ else 0+ in burn (g_selfdestruct + c_new) $ do+ destructs <- use (tx . substate . selfdestructs)+ unless (elem self destructs) $ refund r_selfdestruct+ selfdestruct self+ touchAccount xTo++ if funds /= 0+ then fetchAccount xTo $ \_ -> do+ env . contracts . ix xTo . balance += funds+ assign (env . contracts . ix self . balance) 0+ doStop+ else doStop++ -- op: REVERT+ 0xfd ->+ case stk of+ (xOffset':xSize':_) -> forceConcrete2 (xOffset', xSize') $ \(xOffset, xSize) ->+ accessMemoryRange fees xOffset xSize $ do+ let output = readMemory xOffset xSize vm+ finishFrame (FrameReverted output)+ _ -> underrun++ xxx ->+ vmError (UnrecognizedOpcode xxx)++-- | Checks a *CALL for failure; OOG, too many callframes, memory access etc.+callChecks+ :: (?op :: Word8)+ => Contract -> Word -> Addr -> Word -> Word -> Word -> Word -> Word -> [SymWord]+ -- continuation with gas avail for call+ -> (Word -> EVM ())+ -> EVM ()+callChecks this xGas xContext xValue xInOffset xInSize xOutOffset xOutSize xs continue = do+ vm <- get+ let fees = view (block . schedule) vm+ accessMemoryRange fees xInOffset xInSize $+ accessMemoryRange fees xOutOffset xOutSize $ do+ availableGas <- use (state . gas)+ let recipientExists = accountExists xContext vm+ (cost, gas') = costOfCall fees recipientExists xValue availableGas xGas+ burn (cost - gas') $ do+ if xValue > view balance this+ then do+ assign (state . stack) (0 : xs)+ assign (state . returndata) mempty+ pushTrace $ ErrorTrace $ BalanceTooLow xValue (view balance this)+ next+ else if length (view frames vm) >= 1024+ then do+ assign (state . stack) (0 : xs)+ assign (state . returndata) mempty+ pushTrace $ ErrorTrace $ CallDepthLimitReached+ next+ else continue gas'++precompiledContract+ :: (?op :: Word8)+ => Contract+ -> Word+ -> Addr+ -> Addr+ -> Word+ -> Word -> Word -> Word -> Word+ -> [SymWord]+ -> EVM ()+precompiledContract this xGas precompileAddr recipient xValue inOffset inSize outOffset outSize xs =+ callChecks this xGas recipient xValue inOffset inSize outOffset outSize xs $ \gas' ->+ do+ executePrecompile precompileAddr gas' inOffset inSize outOffset outSize xs+ self <- use (state . contract)+ stk <- use (state . stack)+ case stk of+ (x:_) -> case maybeLitWord x of+ Just 0 ->+ return ()+ Just 1 ->+ fetchAccount recipient $ \_ -> do++ zoom (env . contracts) $ do+ ix self . balance -= xValue+ ix recipient . balance += xValue+ touchAccount self+ touchAccount recipient+ touchAccount precompileAddr+ _ -> vmError UnexpectedSymbolicArg+ _ -> underrun++executePrecompile+ :: (?op :: Word8)+ => Addr+ -> Word -> Word -> Word -> Word -> Word -> [SymWord]+ -> EVM ()+executePrecompile preCompileAddr gasCap inOffset inSize outOffset outSize xs = do+ vm <- get+ let input = readMemory (num inOffset) (num inSize) vm+ fees = view (block . schedule) vm+ cost = costOfPrecompile fees preCompileAddr input+ notImplemented = error $ "precompile at address " <> show preCompileAddr <> " not yet implemented"+ precompileFail = burn (gasCap - cost) $ do+ assign (state . stack) (0 : xs)+ pushTrace $ ErrorTrace $ PrecompileFailure+ next+ if cost > gasCap then+ burn gasCap $ do+ assign (state . stack) (0 : xs)+ next+ else+ burn cost $+ case preCompileAddr of+ -- ECRECOVER+ 0x1 ->+ -- TODO: support symbolic variant+ forceConcreteBuffer input $ \input' ->+ case EVM.Precompiled.execute 0x1 (truncpadlit 128 input') 32 of+ Nothing -> do+ -- return no output for invalid signature+ assign (state . stack) (1 : xs)+ assign (state . returndata) mempty+ next+ Just output -> do+ assign (state . stack) (1 : xs)+ assign (state . returndata) (ConcreteBuffer output)+ copyBytesToMemory (ConcreteBuffer output) outSize 0 outOffset+ next++ -- SHA2-256+ 0x2 ->+ let+ hash = case input of+ ConcreteBuffer input' -> ConcreteBuffer $ BS.pack $ BA.unpack $ (Crypto.hash input' :: Digest SHA256)+ SymbolicBuffer input' -> SymbolicBuffer $ symSHA256 input'+ in do+ assign (state . stack) (1 : xs)+ assign (state . returndata) hash+ copyBytesToMemory hash outSize 0 outOffset+ next++ -- RIPEMD-160+ 0x3 ->+ -- TODO: support symbolic variant+ forceConcreteBuffer input $ \input' ->++ let+ padding = BS.pack $ replicate 12 0+ hash' = BS.pack $ BA.unpack (Crypto.hash input' :: Digest RIPEMD160)+ hash = ConcreteBuffer $ padding <> hash'+ in do+ assign (state . stack) (1 : xs)+ assign (state . returndata) hash+ copyBytesToMemory hash outSize 0 outOffset+ next++ -- IDENTITY+ 0x4 -> do+ assign (state . stack) (1 : xs)+ assign (state . returndata) input+ copyCallBytesToMemory input outSize 0 outOffset+ next++ -- MODEXP+ 0x5 ->+ -- TODO: support symbolic variant+ forceConcreteBuffer input $ \input' ->++ let+ (lenb, lene, lenm) = parseModexpLength input'++ output = ConcreteBuffer $+ case (isZero (96 + lenb + lene) lenm input') of+ True ->+ truncpadlit (num lenm) (asBE (0 :: Int))+ False ->+ let+ b = asInteger $ lazySlice 96 lenb $ input'+ e = asInteger $ lazySlice (96 + lenb) lene $ input'+ m = asInteger $ lazySlice (96 + lenb + lene) lenm $ input'+ in+ padLeft (num lenm) (asBE (expFast b e m))+ in do+ assign (state . stack) (1 : xs)+ assign (state . returndata) output+ copyBytesToMemory output outSize 0 outOffset+ next++ -- ECADD+ 0x6 ->+ -- TODO: support symbolic variant+ forceConcreteBuffer input $ \input' ->+ case EVM.Precompiled.execute 0x6 (truncpadlit 128 input') 64 of+ Nothing -> precompileFail+ Just output -> do+ let truncpaddedOutput = ConcreteBuffer $ truncpadlit 64 output+ assign (state . stack) (1 : xs)+ assign (state . returndata) truncpaddedOutput+ copyBytesToMemory truncpaddedOutput outSize 0 outOffset+ next++ -- ECMUL+ 0x7 ->+ -- TODO: support symbolic variant+ forceConcreteBuffer input $ \input' ->++ case EVM.Precompiled.execute 0x7 (truncpadlit 96 input') 64 of+ Nothing -> precompileFail+ Just output -> do+ let truncpaddedOutput = ConcreteBuffer $ truncpadlit 64 output+ assign (state . stack) (1 : xs)+ assign (state . returndata) truncpaddedOutput+ copyBytesToMemory truncpaddedOutput outSize 0 outOffset+ next++ -- ECPAIRING+ 0x8 ->+ -- TODO: support symbolic variant+ forceConcreteBuffer input $ \input' ->++ case EVM.Precompiled.execute 0x8 input' 32 of+ Nothing -> precompileFail+ Just output -> do+ let truncpaddedOutput = ConcreteBuffer $ truncpadlit 32 output+ assign (state . stack) (1 : xs)+ assign (state . returndata) truncpaddedOutput+ copyBytesToMemory truncpaddedOutput outSize 0 outOffset+ next++ -- BLAKE2+ 0x9 ->+ -- TODO: support symbolic variant+ forceConcreteBuffer input $ \input' -> do++ case (BS.length input', 1 >= BS.last input') of+ (213, True) -> case EVM.Precompiled.execute 0x9 input' 64 of+ Just output -> do+ let truncpaddedOutput = ConcreteBuffer $ truncpadlit 64 output+ assign (state . stack) (1 : xs)+ assign (state . returndata) truncpaddedOutput+ copyBytesToMemory truncpaddedOutput outSize 0 outOffset+ next+ Nothing -> precompileFail+ _ -> precompileFail+++ _ -> notImplemented++truncpadlit :: Int -> ByteString -> ByteString+truncpadlit n xs = if m > n then BS.take n xs+ else BS.append xs (BS.replicate (n - m) 0)+ where m = BS.length xs++lazySlice :: Word -> Word -> ByteString -> LS.ByteString+lazySlice offset size bs =+ let bs' = LS.take (num size) (LS.drop (num offset) (fromStrict bs))+ in bs' <> LS.replicate ((num size) - LS.length bs') 0++parseModexpLength :: ByteString -> (Word, Word, Word)+parseModexpLength input =+ let lenb = w256 $ word $ LS.toStrict $ lazySlice 0 32 input+ lene = w256 $ word $ LS.toStrict $ lazySlice 32 64 input+ lenm = w256 $ word $ LS.toStrict $ lazySlice 64 96 input+ in (lenb, lene, lenm)++isZero :: Word -> Word -> ByteString -> Bool+isZero offset size bs =+ LS.all (== 0) $+ LS.take (num size) $+ LS.drop (num offset) $+ fromStrict bs++asInteger :: LS.ByteString -> Integer+asInteger xs = if xs == mempty then 0+ else 256 * asInteger (LS.init xs)+ + num (LS.last xs)++-- * Opcode helper actions++noop :: Monad m => m ()+noop = pure ()++pushTo :: MonadState s m => ASetter s s [a] [a] -> a -> m ()+pushTo f x = f %= (x :)++pushToSequence :: MonadState s m => ASetter s s (Seq a) (Seq a) -> a -> m ()+pushToSequence f x = f %= (Seq.|> x)++getCodeLocation :: VM -> CodeLocation+getCodeLocation vm = (view (state . contract) vm, view (state . pc) vm)++-- | Construct SMT Query and halt execution until resolved+askSMT :: CodeLocation -> SymWord -> (Bool -> EVM ()) -> EVM ()+askSMT codeloc jumpcondition continue = do+ -- We keep track of how many times we have come across this particular+ -- (contract, pc) combination in the `iteration` mapping.+ iteration <- use (iterations . at codeloc . non 0)++ -- If we are backstepping, the result of this query should be cached+ -- already. So we first check the cache to see if the result is known+ use (cache . path . at (codeloc, iteration)) >>= \case+ -- If the query has been done already, select path or select the only available+ Just w -> choosePath (Iszero w)+ -- If this is a new query, run the query, cache the result+ -- increment the iterations and select appropriate path+ Nothing -> do pathconds <- use pathConditions+ assign result . Just . VMFailure . Query $ PleaseAskSMT+ jumpcondition pathconds choosePath++ where -- Only one path is possible+ choosePath :: JumpCondition -> EVM ()+ choosePath (Iszero v) = do assign result Nothing+ pathConditions <>= if v then [litWord 0 .== jumpcondition] else [litWord 0 ./= jumpcondition]+ iteration <- use (iterations . at codeloc . non 0)+ assign (cache . path . at (codeloc, iteration)) (Just v)+ assign (iterations . at codeloc) (Just (iteration + 1))+ continue v+ -- Both paths are possible; we ask for more input+ choosePath Unknown = assign result . Just . VMFailure . Choose . PleaseChoosePath $ choosePath . Iszero+ -- None of the paths are possible; fail this branch+ choosePath Inconsistent = vmError DeadPath++-- | Construct RPC Query and halt execution until resolved+fetchAccount :: Addr -> (Contract -> EVM ()) -> EVM ()+fetchAccount addr continue =+ use (env . contracts . at addr) >>= \case+ Just c -> continue c+ Nothing ->+ use (cache . fetched . at addr) >>= \case+ Just c -> do+ assign (env . contracts . at addr) (Just c)+ continue c+ Nothing ->+ assign result . Just . VMFailure . Query $+ PleaseFetchContract addr+ (\c -> do assign (cache . fetched . at addr) (Just c)+ assign (env . contracts . at addr) (Just c)+ assign result Nothing+ tryContinue c)+ where+ tryContinue c =+ if (view external c) && (accountEmpty c)+ then vmError . NoSuchContract $ addr+ else continue c++readStorage :: Storage -> SymWord -> Maybe (SymWord)+readStorage (Symbolic s) (S _ loc) = Just . sw256 $ readArray s loc+readStorage (Concrete s) loc = Map.lookup (forceLit loc) s++writeStorage :: SymWord -> SymWord -> Storage -> Storage+writeStorage (S _ loc) (S _ val) (Symbolic s) = Symbolic (writeArray s loc val)+writeStorage loc val (Concrete s) = Concrete (Map.insert (forceLit loc) val s)++accessStorage+ :: Addr -- ^ Contract address+ -> SymWord -- ^ Storage slot key+ -> (SymWord -> EVM ()) -- ^ Continuation+ -> EVM ()+accessStorage addr slot continue =+ use (env . contracts . at addr) >>= \case+ Just c ->+ case readStorage (view storage c) slot of+ -- Notice that if storage is symbolic, we always continue straight away+ Just x ->+ continue x+ Nothing ->+ if view external c+ then+ -- check if the slot is cached+ use (cache . fetched . at addr) >>= \case+ Nothing -> mkQuery+ Just cachedContract ->+ maybe mkQuery continue (readStorage (view storage cachedContract) slot)+ else do+ modifying (env . contracts . ix addr . storage) (writeStorage slot 0)+ continue 0+ Nothing ->+ fetchAccount addr $ \_ ->+ accessStorage addr slot continue+ where+ mkQuery = assign result . Just . VMFailure . Query $+ PleaseFetchSlot addr (forceLit slot)+ (\(litWord -> x) -> do+ modifying (cache . fetched . ix addr . storage) (writeStorage slot x)+ modifying (env . contracts . ix addr . storage) (writeStorage slot x)+ assign result Nothing+ continue x)++accountExists :: Addr -> VM -> Bool+accountExists addr vm =+ case view (env . contracts . at addr) vm of+ Just c -> not (accountEmpty c)+ Nothing -> False++-- EIP 161+accountEmpty :: Contract -> Bool+accountEmpty c =+ (view contractcode c == RuntimeCode mempty)+ && (view nonce c == 0)+ && (view balance c == 0)++-- * How to finalize a transaction+finalize :: EVM ()+finalize = do+ let+ burnRemainingGas = use (state . gas) >>= flip burn noop+ revertContracts = use (tx . txReversion) >>= assign (env . contracts)+ revertSubstate = assign (tx . substate) (SubState mempty mempty mempty)++ use result >>= \case+ Nothing ->+ error "Finalising an unfinished tx."+ Just (VMFailure (Revert _)) -> do+ revertContracts+ revertSubstate+ Just (VMFailure _) -> do+ burnRemainingGas+ revertContracts+ revertSubstate+ Just (VMSuccess output) -> do+ -- deposit the code from a creation tx+ creation <- use (tx . isCreate)+ createe <- use (state . contract)+ createeExists <- (Map.member createe) <$> use (env . contracts)++ when (creation && createeExists) $ forceConcreteBuffer output $ \code' -> replaceCode createe (RuntimeCode code')++ -- compute and pay the refund to the caller and the+ -- corresponding payment to the miner+ txOrigin <- use (tx . origin)+ sumRefunds <- (sum . (snd <$>)) <$> (use (tx . substate . refunds))+ miner <- use (block . coinbase)+ blockReward <- r_block <$> (use (block . schedule))+ gasPrice <- use (tx . gasprice)+ gasLimit <- use (tx . txgaslimit)+ gasRemaining <- use (state . gas)++ let+ gasUsed = gasLimit - gasRemaining+ cappedRefund = min (quot gasUsed 2) sumRefunds+ originPay = (gasRemaining + cappedRefund) * gasPrice+ minerPay = gasPrice * (gasUsed - cappedRefund)++ modifying (env . contracts)+ (Map.adjust (over balance (+ originPay)) txOrigin)+ modifying (env . contracts)+ (Map.adjust (over balance (+ minerPay)) miner)+ touchAccount miner++ -- pay out the block reward, recreating the miner if necessary+ preuse (env . contracts . ix miner) >>= \case+ Nothing -> modifying (env . contracts)+ (Map.insert miner (initialContract (EVM.RuntimeCode mempty)))+ Just _ -> noop+ modifying (env . contracts)+ (Map.adjust (over balance (+ blockReward)) miner)++ -- perform state trie clearing (EIP 161), of selfdestructs+ -- and touched accounts. addresses are cleared if they have+ -- a) selfdestructed, or+ -- b) been touched and+ -- c) are empty.+ -- (see Yellow Paper "Accrued Substate")+ --+ -- remove any destructed addresses+ destroyedAddresses <- use (tx . substate . selfdestructs)+ modifying (env . contracts)+ (Map.filterWithKey (\k _ -> (notElem k destroyedAddresses)))+ -- then, clear any remaining empty and touched addresses+ touchedAddresses <- use (tx . substate . touchedAccounts)+ modifying (env . contracts)+ (Map.filterWithKey+ (\k a -> not ((elem k touchedAddresses) && accountEmpty a)))++-- | Loads the selected contract as the current contract to execute+loadContract :: Addr -> EVM ()+loadContract target =+ preuse (env . contracts . ix target . contractcode) >>=+ \case+ Nothing ->+ error "Call target doesn't exist"+ Just (InitCode targetCode) -> do+ assign (state . contract) target+ assign (state . code) targetCode+ assign (state . codeContract) target+ Just (RuntimeCode targetCode) -> do+ assign (state . contract) target+ assign (state . code) targetCode+ assign (state . codeContract) target++limitStack :: Int -> EVM () -> EVM ()+limitStack n continue = do+ stk <- use (state . stack)+ if length stk + n > 1024+ then vmError StackLimitExceeded+ else continue++notStatic :: EVM () -> EVM ()+notStatic continue = do+ bad <- use (state . static)+ if bad+ then vmError StateChangeWhileStatic+ else continue++-- | Burn gas, failing if insufficient gas is available+burn :: Word -> EVM () -> EVM ()+burn n continue = do+ available <- use (state . gas)+ if n <= available+ then do+ state . gas -= n+ burned += n+ continue+ else+ vmError (OutOfGas available n)++forceConcreteAddr :: SAddr -> (Addr -> EVM ()) -> EVM ()+forceConcreteAddr n continue = case maybeLitAddr n of+ Nothing -> vmError UnexpectedSymbolicArg+ Just c -> continue c++forceConcrete :: SymWord -> (Word -> EVM ()) -> EVM ()+forceConcrete n continue = case maybeLitWord n of+ Nothing -> vmError UnexpectedSymbolicArg+ Just c -> continue c++forceConcrete2 :: (SymWord, SymWord) -> ((Word, Word) -> EVM ()) -> EVM ()+forceConcrete2 (n,m) continue = case (maybeLitWord n, maybeLitWord m) of+ (Just c, Just d) -> continue (c, d)+ _ -> vmError UnexpectedSymbolicArg++forceConcrete3 :: (SymWord, SymWord, SymWord) -> ((Word, Word, Word) -> EVM ()) -> EVM ()+forceConcrete3 (k,n,m) continue = case (maybeLitWord k, maybeLitWord n, maybeLitWord m) of+ (Just c, Just d, Just f) -> continue (c, d, f)+ _ -> vmError UnexpectedSymbolicArg++forceConcrete4 :: (SymWord, SymWord, SymWord, SymWord) -> ((Word, Word, Word, Word) -> EVM ()) -> EVM ()+forceConcrete4 (k,l,n,m) continue = case (maybeLitWord k, maybeLitWord l, maybeLitWord n, maybeLitWord m) of+ (Just b, Just c, Just d, Just f) -> continue (b, c, d, f)+ _ -> vmError UnexpectedSymbolicArg++forceConcrete5 :: (SymWord, SymWord, SymWord, SymWord, SymWord) -> ((Word, Word, Word, Word, Word) -> EVM ()) -> EVM ()+forceConcrete5 (k,l,m,n,o) continue = case (maybeLitWord k, maybeLitWord l, maybeLitWord m, maybeLitWord n, maybeLitWord o) of+ (Just a, Just b, Just c, Just d, Just e) -> continue (a, b, c, d, e)+ _ -> vmError UnexpectedSymbolicArg++forceConcrete6 :: (SymWord, SymWord, SymWord, SymWord, SymWord, SymWord) -> ((Word, Word, Word, Word, Word, Word) -> EVM ()) -> EVM ()+forceConcrete6 (k,l,m,n,o,p) continue = case (maybeLitWord k, maybeLitWord l, maybeLitWord m, maybeLitWord n, maybeLitWord o, maybeLitWord p) of+ (Just a, Just b, Just c, Just d, Just e, Just f) -> continue (a, b, c, d, e, f)+ _ -> vmError UnexpectedSymbolicArg++forceConcreteBuffer :: Buffer -> (ByteString -> EVM ()) -> EVM ()+forceConcreteBuffer (SymbolicBuffer b) continue = case maybeLitBytes b of+ Nothing -> vmError UnexpectedSymbolicArg+ Just bs -> continue bs+forceConcreteBuffer (ConcreteBuffer b) continue = continue b++-- * Substate manipulation+refund :: Word -> EVM ()+refund n = do+ self <- use (state . contract)+ pushTo (tx . substate . refunds) (self, n)++unRefund :: Word -> EVM ()+unRefund n = do+ self <- use (state . contract)+ refs <- use (tx . substate . refunds)+ assign (tx . substate . refunds)+ (filter (\(a,b) -> not (a == self && b == n)) refs)++touchAccount :: Addr -> EVM()+touchAccount = pushTo ((tx . substate) . touchedAccounts)++selfdestruct :: Addr -> EVM()+selfdestruct = pushTo ((tx . substate) . selfdestructs)++-- * Cheat codes++-- The cheat code is 7109709ecfa91a80626ff3989d68f67f5b1dd12d.+-- Call this address using one of the cheatActions below to do+-- special things, e.g. changing the block timestamp. Beware that+-- these are necessarily hevm specific.+cheatCode :: Addr+cheatCode = num (keccak "hevm cheat code")++cheat+ :: (?op :: Word8)+ => (Word, Word) -> (Word, Word)+ -> EVM ()+cheat (inOffset, inSize) (outOffset, outSize) = do+ mem <- use (state . memory)+ vm <- get+ let+ abi = readMemoryWord32 inOffset mem+ input = readMemory (inOffset + 4) (inSize - 4) vm+ case fromSized <$> unliteral abi of+ Nothing -> vmError UnexpectedSymbolicArg+ Just abi ->+ case Map.lookup abi cheatActions of+ Nothing ->+ vmError (BadCheatCode (Just abi))+ Just (argTypes, action) ->+ case input of+ SymbolicBuffer _ -> vmError UnexpectedSymbolicArg+ ConcreteBuffer input' ->+ case runGetOrFail+ (getAbiSeq (length argTypes) argTypes)+ (LS.fromStrict input') of+ Right ("", _, args) ->+ action (toList args) >>= \case+ Nothing -> do+ next+ push 1+ Just (encodeAbiValue -> bs) -> do+ next+ modifying (state . memory)+ (writeMemory (ConcreteBuffer bs) outSize 0 outOffset)+ push 1+ _ ->+ vmError (BadCheatCode (Just abi))++type CheatAction = ([AbiType], [AbiValue] -> EVM (Maybe AbiValue))++cheatActions :: Map Word32 CheatAction+cheatActions =+ Map.fromList+ [ action "warp(uint256)" [AbiUIntType 256] $+ \[AbiUInt 256 x] -> do+ assign (block . timestamp) (w256 (W256 x))+ return Nothing,+ action "store(address,bytes32,bytes32)" [AbiAddressType, AbiBytesType 32, AbiBytesType 32] $+ \[AbiAddress a, AbiBytes 32 x, AbiBytes 32 y] -> do+ let slot = w256lit $ word x+ new = w256lit $ word y+ fetchAccount a $ \_ -> do+ modifying (env . contracts . ix a . storage) (writeStorage slot new)+ return Nothing+ ]+ where+ action s ts f = (abiKeccak s, (ts, f))++-- * General call implementation ("delegateCall")+delegateCall+ :: (?op :: Word8)+ => Contract -> Word -> Addr -> Addr -> Word -> Word -> Word -> Word -> Word -> [SymWord]+ -> EVM ()+ -> EVM ()+delegateCall this gasGiven xTo xContext xValue xInOffset xInSize xOutOffset xOutSize xs continue =+ callChecks this gasGiven xContext xValue xInOffset xInSize xOutOffset xOutSize xs $+ \xGas -> do+ vm0 <- get+ fetchAccount xTo . const $+ preuse (env . contracts . ix xTo) >>= \case+ Nothing ->+ vmError (NoSuchContract xTo)+ Just target ->+ burn xGas $ do+ let newContext = CallContext+ { callContextOffset = xOutOffset+ , callContextSize = xOutSize+ , callContextCodehash = view codehash target+ , callContextReversion = view (env . contracts) vm0+ , callContextSubState = view (tx . substate) vm0+ , callContextAbi =+ if xInSize >= 4+ then case unliteral $ readMemoryWord32 xInOffset (view (state . memory) vm0)+ of Nothing -> Nothing+ Just abi -> Just . w256 $ num abi+ else Nothing+ , callContextData = (readMemory (num xInOffset) (num xInSize) vm0)+ }++ pushTrace (FrameTrace newContext)+ next+ vm1 <- get++ pushTo frames $ Frame+ { _frameState = (set stack xs) (view state vm1)+ , _frameContext = newContext+ }++ zoom state $ do+ assign gas xGas+ assign pc 0+ assign code (view bytecode target)+ assign codeContract xTo+ assign stack mempty+ assign memory mempty+ assign memorySize 0+ assign returndata mempty+ assign calldata (readMemory (num xInOffset) (num xInSize) vm0, literal (num xInSize))++ continue++-- -- * Contract creation++-- EIP 684+collision :: Maybe Contract -> Bool+collision c' = case c' of+ Just c -> (view contractcode c /= RuntimeCode mempty) || (view nonce c /= 0)+ Nothing -> False++create :: (?op :: Word8)+ => Addr -> Contract+ -> Word -> Word -> [SymWord] -> Addr -> ByteString -> EVM ()+create self this xGas xValue xs newAddr initCode = do+ vm0 <- get+ if xValue > view balance this+ then do+ assign (state . stack) (0 : xs)+ assign (state . returndata) mempty+ pushTrace $ ErrorTrace $ BalanceTooLow xValue (view balance this)+ next+ else if length (view frames vm0) >= 1024+ then do+ assign (state . stack) (0 : xs)+ assign (state . returndata) mempty+ pushTrace $ ErrorTrace $ CallDepthLimitReached+ next+ else if collision $ view (env . contracts . at newAddr) vm0+ then burn xGas $ do+ assign (state . stack) (0 : xs)+ modifying (env . contracts . ix self . nonce) succ+ next+ else burn xGas $ do+ touchAccount self+ touchAccount newAddr+ let+ store = case view (env . storageModel) vm0 of+ ConcreteS -> Concrete mempty+ SymbolicS -> Symbolic $ sListArray 0 []+ InitialS -> Symbolic $ sListArray 0 []+ newContract =+ initialContract (InitCode initCode) & set storage store+ newContext =+ CreationContext { creationContextCodehash = view codehash newContract+ , creationContextReversion = view (env . contracts) vm0+ , creationContextSubstate = view (tx . substate) vm0+ }++ zoom (env . contracts) $ do+ oldAcc <- use (at newAddr)+ let oldBal = case oldAcc of+ Nothing -> 0+ Just c -> view balance c+ assign (at newAddr) (Just newContract)+ assign (ix newAddr . balance) (oldBal + xValue)+ assign (ix newAddr . nonce) 1+ modifying (ix self . balance) (flip (-) xValue)+ modifying (ix self . nonce) succ++ pushTrace (FrameTrace newContext)+ next+ vm1 <- get+ pushTo frames $ Frame+ { _frameContext = newContext+ , _frameState = (set stack xs) (view state vm1)+ }++ assign state $+ blankState+ & set contract newAddr+ & set codeContract newAddr+ & set code initCode+ & set callvalue (litWord xValue)+ & set caller (litAddr self)+ & set gas xGas++-- | Replace a contract's code, like when CREATE returns+-- from the constructor code.+replaceCode :: Addr -> ContractCode -> EVM ()+replaceCode target newCode =+ zoom (env . contracts . at target) $+ get >>= \case+ Just now -> case (view contractcode now) of+ InitCode _ ->+ put . Just $+ initialContract newCode+ & set storage (view storage now)+ & set balance (view balance now)+ & set nonce (view nonce now)+ RuntimeCode _ ->+ error "internal error: can't replace code of deployed contract"+ Nothing ->+ error "internal error: can't replace code of nonexistent contract"++replaceCodeOfSelf :: ContractCode -> EVM ()+replaceCodeOfSelf newCode = do+ vm <- get+ replaceCode (view (state . contract) vm) newCode++resetState :: EVM ()+resetState = do+ assign result Nothing+ assign frames []+ assign state blankState+++-- * VM error implementation++vmError :: Error -> EVM ()+vmError e = finishFrame (FrameErrored e)++underrun :: EVM ()+underrun = vmError StackUnderrun++-- | A stack frame can be popped in three ways.+data FrameResult+ = FrameReturned Buffer -- ^ STOP, RETURN, or no more code+ | FrameReverted Buffer -- ^ REVERT+ | FrameErrored Error -- ^ Any other error+ deriving Show++-- | This function defines how to pop the current stack frame in either of+-- the ways specified by 'FrameResult'.+--+-- It also handles the case when the current stack frame is the only one;+-- in this case, we set the final '_result' of the VM execution.+finishFrame :: FrameResult -> EVM ()+finishFrame how = do+ oldVm <- get++ case view frames oldVm of+ -- Is the current frame the only one?+ [] -> do+ case how of+ FrameReturned output -> assign result . Just $ VMSuccess output+ FrameReverted buffer -> forceConcreteBuffer buffer $ \out -> assign result . Just $ VMFailure (Revert out)+ FrameErrored e -> assign result . Just $ VMFailure e+ finalize++ -- Are there some remaining frames?+ nextFrame : remainingFrames -> do++ -- Pop the top frame.+ assign frames remainingFrames+ -- Install the state of the frame to which we shall return.+ assign state (view frameState nextFrame)+ -- Insert a debug trace.+ insertTrace $+ case how of+ FrameErrored e ->+ ErrorTrace e+ FrameReverted (ConcreteBuffer output) ->+ ErrorTrace (Revert output)+ FrameReverted (SymbolicBuffer output) ->+ ErrorTrace (Revert (forceLitBytes output))+ FrameReturned output ->+ ReturnTrace output (view frameContext nextFrame)+ -- Pop to the previous level of the debug trace stack.+ popTrace++ -- When entering a call, the gas allowance is counted as burned+ -- in advance; this unburns the remainder and adds it to the+ -- parent frame.+ let remainingGas = view (state . gas) oldVm+ reclaimRemainingGasAllowance = do+ modifying burned (subtract remainingGas)+ modifying (state . gas) (+ remainingGas)++ FeeSchedule {..} = view ( block . schedule ) oldVm++ -- Now dispatch on whether we were creating or calling,+ -- and whether we shall return, revert, or error (six cases).+ case view frameContext nextFrame of++ -- Were we calling?+ CallContext (num -> outOffset) (num -> outSize) _ _ _ reversion substate' -> do++ let+ revertContracts = assign (env . contracts) reversion+ revertSubstate = assign (tx . substate) substate'++ case how of+ -- Case 1: Returning from a call?+ FrameReturned output -> do+ assign (state . returndata) output+ copyCallBytesToMemory output outSize 0 outOffset+ reclaimRemainingGasAllowance+ push 1++ -- Case 2: Reverting during a call?+ FrameReverted output -> do+ revertContracts+ revertSubstate+ assign (state . returndata) output+ copyCallBytesToMemory output outSize 0 outOffset+ reclaimRemainingGasAllowance+ push 0++ -- Case 3: Error during a call?+ FrameErrored _ -> do+ revertContracts+ revertSubstate+ assign (state . returndata) mempty+ push 0++ -- Or were we creating?+ CreationContext _ reversion substate' -> do+ creator <- use (state . contract)+ let+ createe = view (state . contract) oldVm+ revertContracts = assign (env . contracts) reversion'+ revertSubstate = assign (tx . substate) substate'++ -- persist the nonce through the reversion+ reversion' = (Map.adjust (over nonce (+ 1)) creator) reversion++ case how of+ -- Case 4: Returning during a creation?+ FrameReturned output ->+ forceConcreteBuffer output $ \output' -> do+ replaceCode createe (RuntimeCode output')+ assign (state . returndata) mempty+ reclaimRemainingGasAllowance+ push (num createe)++ -- Case 5: Reverting during a creation?+ FrameReverted output -> do+ revertContracts+ revertSubstate+ assign (state . returndata) output+ reclaimRemainingGasAllowance+ push 0++ -- Case 6: Error during a creation?+ FrameErrored _ -> do+ revertContracts+ revertSubstate+ assign (state . returndata) mempty+ push 0+++-- * Memory helpers++accessUnboundedMemoryRange+ :: FeeSchedule Word+ -> Word+ -> Word+ -> EVM ()+ -> EVM ()+accessUnboundedMemoryRange _ _ 0 continue = continue+accessUnboundedMemoryRange fees f l continue = do+ m0 <- num <$> use (state . memorySize)+ do+ let m1 = 32 * ceilDiv (max m0 (num f + num l)) 32+ burn (memoryCost fees m1 - memoryCost fees m0) $ do+ assign (state . memorySize) (num m1)+ continue++accessMemoryRange+ :: FeeSchedule Word+ -> Word+ -> Word+ -> EVM ()+ -> EVM ()+accessMemoryRange _ _ 0 continue = continue+accessMemoryRange fees f l continue =+ if f + l < l+ then vmError IllegalOverflow+ else accessUnboundedMemoryRange fees f l continue++accessMemoryWord+ :: FeeSchedule Word -> Word -> EVM () -> EVM ()+accessMemoryWord fees x = accessMemoryRange fees x 32++copyBytesToMemory+ :: Buffer -> Word -> Word -> Word -> EVM ()+copyBytesToMemory bs size xOffset yOffset =+ if size == 0 then noop+ else do+ mem <- use (state . memory)+ assign (state . memory) $+ writeMemory bs size xOffset yOffset mem++copyCallBytesToMemory+ :: Buffer -> Word -> Word -> Word -> EVM ()+copyCallBytesToMemory bs size xOffset yOffset =+ if size == 0 then noop+ else do+ mem <- use (state . memory)+ assign (state . memory) $+ writeMemory bs (min size (num (len bs))) xOffset yOffset mem++readMemory :: Word -> Word -> VM -> Buffer+readMemory offset size vm = sliceWithZero (num offset) (num size) (view (state . memory) vm)++word256At+ :: Functor f+ => Word -> (SymWord -> f (SymWord))+ -> Buffer -> f Buffer+word256At i = lens getter setter where+ getter = readMemoryWord i+ setter m x = setMemoryWord i x m++-- * Tracing++withTraceLocation+ :: (MonadState VM m) => TraceData -> m Trace+withTraceLocation x = do+ vm <- get+ let+ Just this =+ preview (env . contracts . ix (view (state . codeContract) vm)) vm+ pure Trace+ { _traceData = x+ , _traceCodehash = view codehash this+ , _traceOpIx = (view opIxMap this) Vector.! (view (state . pc) vm)+ }++pushTrace :: TraceData -> EVM ()+pushTrace x = do+ trace <- withTraceLocation x+ modifying traces $+ \t -> Zipper.children $ Zipper.insert (Node trace []) t++insertTrace :: TraceData -> EVM ()+insertTrace x = do+ trace <- withTraceLocation x+ modifying traces $+ \t -> Zipper.nextSpace $ Zipper.insert (Node trace []) t++popTrace :: EVM ()+popTrace =+ modifying traces $+ \t -> case Zipper.parent t of+ Nothing -> error "internal error (trace root)"+ Just t' -> Zipper.nextSpace t'++zipperRootForest :: Zipper.TreePos Zipper.Empty a -> Forest a+zipperRootForest z =+ case Zipper.parent z of+ Nothing -> Zipper.toForest z+ Just z' -> zipperRootForest (Zipper.nextSpace z')++traceForest :: VM -> Forest Trace+traceForest = view (traces . to zipperRootForest)++traceLog :: (MonadState VM m) => Log -> m ()+traceLog log = do+ trace <- withTraceLocation (EventTrace log)+ modifying traces $+ \t -> Zipper.nextSpace (Zipper.insert (Node trace []) t)++-- * Stack manipulation++push :: Word -> EVM ()+push = pushSym . w256lit . num++pushSym :: SymWord -> EVM ()+pushSym x = state . stack %= (x :)+++stackOp1+ :: (?op :: Word8)+ => ((SymWord) -> Word)+ -> ((SymWord) -> (SymWord))+ -> EVM ()+stackOp1 cost f =+ use (state . stack) >>= \case+ (x:xs) ->+ burn (cost x) $ do+ next+ let !y = f x+ state . stack .= y : xs+ _ ->+ underrun++stackOp2+ :: (?op :: Word8)+ => (((SymWord), (SymWord)) -> Word)+ -> (((SymWord), (SymWord)) -> (SymWord))+ -> EVM ()+stackOp2 cost f =+ use (state . stack) >>= \case+ (x:y:xs) ->+ burn (cost (x, y)) $ do+ next+ state . stack .= f (x, y) : xs+ _ ->+ underrun++stackOp3+ :: (?op :: Word8)+ => (((SymWord), (SymWord), (SymWord)) -> Word)+ -> (((SymWord), (SymWord), (SymWord)) -> (SymWord))+ -> EVM ()+stackOp3 cost f =+ use (state . stack) >>= \case+ (x:y:z:xs) ->+ burn (cost (x, y, z)) $ do+ next+ state . stack .= f (x, y, z) : xs+ _ ->+ underrun++-- * Bytecode data functions++checkJump :: (Integral n) => n -> [SymWord] -> EVM ()+checkJump x xs = do+ theCode <- use (state . code)+ self <- use (state . codeContract)+ theCodeOps <- use (env . contracts . ix self . codeOps)+ if x < num (BS.length theCode) && BS.index theCode (num x) == 0x5b+ then+ case RegularVector.find (\(i, op) -> i == num x && op == OpJumpdest) theCodeOps of+ Nothing -> vmError BadJumpDestination+ _ -> do+ state . stack .= xs+ state . pc .= num x+ else vmError BadJumpDestination++opSize :: Word8 -> Int+opSize x | x >= 0x60 && x <= 0x7f = num x - 0x60 + 2+opSize _ = 1++-- Index i of the resulting vector contains the operation index for+-- the program counter value i. This is needed because source map+-- entries are per operation, not per byte.+mkOpIxMap :: ByteString -> Vector Int+mkOpIxMap xs = Vector.create $ Vector.new (BS.length xs) >>= \v ->+ -- Loop over the byte string accumulating a vector-mutating action.+ -- This is somewhat obfuscated, but should be fast.+ let (_, _, _, m) =+ BS.foldl' (go v) (0 :: Word8, 0, 0, return ()) xs+ in m >> return v+ where+ go v (0, !i, !j, !m) x | x >= 0x60 && x <= 0x7f =+ {- Start of PUSH op. -} (x - 0x60 + 1, i + 1, j, m >> Vector.write v i j)+ go v (1, !i, !j, !m) _ =+ {- End of PUSH op. -} (0, i + 1, j + 1, m >> Vector.write v i j)+ go v (0, !i, !j, !m) _ =+ {- Other op. -} (0, i + 1, j + 1, m >> Vector.write v i j)+ go v (n, !i, !j, !m) _ =+ {- PUSH data. -} (n - 1, i + 1, j, m >> Vector.write v i j)++vmOp :: VM -> Maybe Op+vmOp vm =+ let i = vm ^. state . pc+ xs = BS.drop i (vm ^. state . code)+ op = BS.index xs 0+ in if BS.null xs+ then Nothing+ else Just (readOp op (BS.drop 1 xs))++vmOpIx :: VM -> Maybe Int+vmOpIx vm =+ do self <- currentContract vm+ (view opIxMap self) Vector.!? (view (state . pc) vm)++opParams :: VM -> Map String (SymWord)+opParams vm =+ case vmOp vm of+ Just OpCreate ->+ params $ words "value offset size"+ Just OpCall ->+ params $ words "gas to value in-offset in-size out-offset out-size"+ Just OpSstore ->+ params $ words "index value"+ Just OpCodecopy ->+ params $ words "mem-offset code-offset code-size"+ Just OpSha3 ->+ params $ words "offset size"+ Just OpCalldatacopy ->+ params $ words "to from size"+ Just OpExtcodecopy ->+ params $ words "account mem-offset code-offset code-size"+ Just OpReturn ->+ params $ words "offset size"+ Just OpJumpi ->+ params $ words "destination condition"+ _ -> mempty+ where+ params xs =+ if length (vm ^. state . stack) >= length xs+ then Map.fromList (zip xs (vm ^. state . stack))+ else mempty++readOp :: Word8 -> ByteString -> Op+readOp x _ | x >= 0x80 && x <= 0x8f = OpDup (x - 0x80 + 1)+readOp x _ | x >= 0x90 && x <= 0x9f = OpSwap (x - 0x90 + 1)+readOp x _ | x >= 0xa0 && x <= 0xa4 = OpLog (x - 0xa0)+readOp x xs | x >= 0x60 && x <= 0x7f =+ let n = x - 0x60 + 1+ xs' = BS.take (num n) xs+ in OpPush (word xs')+readOp x _ = case x of+ 0x00 -> OpStop+ 0x01 -> OpAdd+ 0x02 -> OpMul+ 0x03 -> OpSub+ 0x04 -> OpDiv+ 0x05 -> OpSdiv+ 0x06 -> OpMod+ 0x07 -> OpSmod+ 0x08 -> OpAddmod+ 0x09 -> OpMulmod+ 0x0a -> OpExp+ 0x0b -> OpSignextend+ 0x10 -> OpLt+ 0x11 -> OpGt+ 0x12 -> OpSlt+ 0x13 -> OpSgt+ 0x14 -> OpEq+ 0x15 -> OpIszero+ 0x16 -> OpAnd+ 0x17 -> OpOr+ 0x18 -> OpXor+ 0x19 -> OpNot+ 0x1a -> OpByte+ 0x1b -> OpShl+ 0x1c -> OpShr+ 0x1d -> OpSar+ 0x20 -> OpSha3+ 0x30 -> OpAddress+ 0x31 -> OpBalance+ 0x32 -> OpOrigin+ 0x33 -> OpCaller+ 0x34 -> OpCallvalue+ 0x35 -> OpCalldataload+ 0x36 -> OpCalldatasize+ 0x37 -> OpCalldatacopy+ 0x38 -> OpCodesize+ 0x39 -> OpCodecopy+ 0x3a -> OpGasprice+ 0x3b -> OpExtcodesize+ 0x3c -> OpExtcodecopy+ 0x3d -> OpReturndatasize+ 0x3e -> OpReturndatacopy+ 0x3f -> OpExtcodehash+ 0x40 -> OpBlockhash+ 0x41 -> OpCoinbase+ 0x42 -> OpTimestamp+ 0x43 -> OpNumber+ 0x44 -> OpDifficulty+ 0x45 -> OpGaslimit+ 0x46 -> OpChainid+ 0x47 -> OpSelfbalance+ 0x50 -> OpPop+ 0x51 -> OpMload+ 0x52 -> OpMstore+ 0x53 -> OpMstore8+ 0x54 -> OpSload+ 0x55 -> OpSstore+ 0x56 -> OpJump+ 0x57 -> OpJumpi+ 0x58 -> OpPc+ 0x59 -> OpMsize+ 0x5a -> OpGas+ 0x5b -> OpJumpdest+ 0xf0 -> OpCreate+ 0xf1 -> OpCall+ 0xf2 -> OpCallcode+ 0xf3 -> OpReturn+ 0xf4 -> OpDelegatecall+ 0xf5 -> OpCreate2+ 0xfd -> OpRevert+ 0xfa -> OpStaticcall+ 0xff -> OpSelfdestruct+ _ -> OpUnknown x++mkCodeOps :: ByteString -> RegularVector.Vector (Int, Op)+mkCodeOps bytes = RegularVector.fromList . toList $ go 0 bytes+ where+ go !i !xs =+ case BS.uncons xs of+ Nothing ->+ mempty+ Just (x, xs') ->+ let j = opSize x+ in (i, readOp x xs') Seq.<| go (i + j) (BS.drop j xs)++-- * Gas cost calculation helpers++-- Gas cost function for CALL, transliterated from the Yellow Paper.+costOfCall+ :: FeeSchedule Word+ -> Bool -> Word -> Word -> Word+ -> (Word, Word)+costOfCall (FeeSchedule {..}) recipientExists xValue availableGas xGas =+ (c_gascap + c_extra, c_callgas)+ where+ c_extra =+ num g_call + c_xfer + c_new+ c_xfer =+ if xValue /= 0 then num g_callvalue else 0+ c_callgas =+ if xValue /= 0 then c_gascap + num g_callstipend else c_gascap+ c_new =+ if not recipientExists && xValue /= 0+ then num g_newaccount+ else 0+ c_gascap =+ if availableGas >= c_extra+ then min xGas (allButOne64th (availableGas - c_extra))+ else xGas++-- Gas cost of create, including hash cost if needed+costOfCreate+ :: FeeSchedule Word+ -> Word -> Word -> (Word, Word)+costOfCreate (FeeSchedule {..}) availableGas hashSize =+ (createCost + initGas, initGas)+ where+ createCost = g_create + hashCost+ hashCost = g_sha3word * ceilDiv (hashSize) 32+ initGas = allButOne64th (availableGas - createCost)++-- Gas cost of precompiles+costOfPrecompile :: FeeSchedule Word -> Addr -> Buffer -> Word+costOfPrecompile (FeeSchedule {..}) precompileAddr input =+ case precompileAddr of+ -- ECRECOVER+ 0x1 -> 3000+ -- SHA2-256+ 0x2 -> num $ (((len input + 31) `div` 32) * 12) + 60+ -- RIPEMD-160+ 0x3 -> num $ (((len input + 31) `div` 32) * 120) + 600+ -- IDENTITY+ 0x4 -> num $ (((len input + 31) `div` 32) * 3) + 15+ -- MODEXP+ 0x5 -> num $ (f (num (max lenm lenb)) * num (max lene' 1)) `div` (num g_quaddivisor)+ where input' = case input of+ SymbolicBuffer _ -> error "unsupported: symbolic MODEXP gas cost calc"+ ConcreteBuffer b -> b+ (lenb, lene, lenm) = parseModexpLength input'+ lene' | lene <= 32 && ez = 0+ | lene <= 32 = num (log2 e')+ | e' == 0 = 8 * (lene - 32)+ | otherwise = num (log2 e') + 8 * (lene - 32)++ ez = isZero (96 + lenb) lene input'+ e' = w256 $ word $ LS.toStrict $+ lazySlice (96 + lenb) (min 32 lene) input'++ f :: Integer -> Integer+ f x | x <= 64 = x * x+ | x <= 1024 = (x * x) `div` 4 + 96 * x - 3072+ | otherwise = (x * x) `div` 16 + 480 * x - 199680+ -- ECADD+ 0x6 -> g_ecadd+ -- ECMUL+ 0x7 -> g_ecmul+ -- ECPAIRING+ 0x8 -> num $ ((len input) `div` 192) * (num g_pairing_point) + (num g_pairing_base)+ -- BLAKE2+ 0x9 -> let input' = case input of+ SymbolicBuffer _ -> error "unsupported: symbolic BLAKE2B gas cost calc"+ ConcreteBuffer b -> b+ in g_fround * (num $ asInteger $ lazySlice 0 4 input')+ _ -> error ("unimplemented precompiled contract " ++ show precompileAddr)++-- Gas cost of memory expansion+memoryCost :: FeeSchedule Word -> Word -> Word+memoryCost FeeSchedule{..} byteCount =+ let+ wordCount = ceilDiv byteCount 32+ linearCost = g_memory * wordCount+ quadraticCost = div (wordCount * wordCount) 512+ in+ linearCost + quadraticCost++-- * Uninterpreted functions++symSHA256N :: SInteger -> SInteger -> SWord 256+symSHA256N = uninterpret "sha256"++symkeccakN :: SInteger -> SInteger -> SWord 256+symkeccakN = uninterpret "keccak"++toSInt :: [SWord 8] -> SInteger+toSInt bs = sum $ zipWith (\a i -> sFromIntegral a * 256 ^ i) bs [0..]++-- | Although we'd like to define this directly as an uninterpreted function,+-- we cannot because [a] is not a symbolic type. We must convert the list into a suitable+-- symbolic type first. The only important property of this conversion is that it is injective.+-- We embedd the bytestring as a pair of symbolic integers, this is a fairly easy solution.+symkeccak' :: [SWord 8] -> SWord 256+symkeccak' bytes = case length bytes of+ 0 -> literal $ toSizzle $ keccak ""+ n -> symkeccakN (num n) (toSInt bytes)++symSHA256 :: [SWord 8] -> [SWord 8]+symSHA256 bytes = case length bytes of+ 0 -> litBytes $ BS.pack $ BA.unpack $ (Crypto.hash BS.empty :: Digest SHA256)+ n -> toBytes $ symSHA256N (num n) (toSInt bytes)+ -- * Arithmetic
@@ -25,9 +25,7 @@ -} -{-# Language DeriveAnyClass #-} {-# Language StrictData #-}-{-# Language TemplateHaskell #-} module EVM.ABI ( AbiValue (..)@@ -38,35 +36,47 @@ , putAbi , getAbi , getAbiSeq+ , genAbiValue , abiValueType , abiTypeSolidity , abiCalldata+ , abiMethod+ , emptyAbi , encodeAbiValue+ , decodeAbiValue , parseTypeName+ , makeAbiValue+ , parseAbiValue+ , selector ) where import EVM.Keccak (abiKeccak)-import EVM.Types ()+import EVM.Types import Control.Monad (replicateM, replicateM_, forM_, void)-import Data.Binary.Get (Get, label, getWord8, getWord32be, skip)+import Data.Binary.Get (Get, runGet, label, getWord8, getWord32be, skip) import Data.Binary.Put (Put, runPut, putWord8, putWord32be) import Data.Bits (shiftL, shiftR, (.&.)) import Data.ByteString (ByteString)-import Data.DoubleWord (Word256, Int256, Word160, signedWord)+import Data.DoubleWord (Word256, Int256, signedWord)+import Data.Functor (($>)) import Data.Monoid ((<>)) import Data.Text (Text, pack) import Data.Text.Encoding (encodeUtf8) import Data.Vector (Vector)-import Data.Word (Word32, Word8)+import Data.Word (Word32)+import Data.List (intercalate) import GHC.Generics import Test.QuickCheck hiding ((.&.), label)+import Text.ParserCombinators.ReadP+import Control.Applicative -import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as BSLazy-import qualified Data.Text as Text-import qualified Data.Vector as Vector+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as Char8+import qualified Data.ByteString.Lazy as BSLazy+import qualified Data.Text as Text+import qualified Data.Vector as Vector import qualified Text.Megaparsec as P import qualified Text.Megaparsec.Char as P@@ -74,15 +84,32 @@ data AbiValue = AbiUInt Int Word256 | AbiInt Int Int256- | AbiAddress Word160+ | AbiAddress Addr | AbiBool Bool | AbiBytes Int BS.ByteString | AbiBytesDynamic BS.ByteString | AbiString BS.ByteString | AbiArrayDynamic AbiType (Vector AbiValue) | AbiArray Int AbiType (Vector AbiValue)- deriving (Show, Read, Eq, Ord, Generic)+ | AbiTuple (Vector AbiValue)+ deriving (Read, Eq, Ord, Generic) +-- | Pretty-print some 'AbiValue'.+instance Show AbiValue where+ show (AbiUInt _ n) = show n+ show (AbiInt _ n) = show n+ show (AbiAddress n) = show n+ show (AbiBool b) = if b then "true" else "false"+ show (AbiBytes _ b) = show (ByteStringS b)+ show (AbiBytesDynamic b) = show (ByteStringS b)+ show (AbiString s) = show s+ show (AbiArrayDynamic _ v) =+ "[" ++ intercalate ", " (show <$> Vector.toList v) ++ "]"+ show (AbiArray _ _ v) =+ "[" ++ intercalate ", " (show <$> Vector.toList v) ++ "]"+ show (AbiTuple v) =+ "(" ++ intercalate ", " (show <$> Vector.toList v) ++ ")"+ data AbiType = AbiUIntType Int | AbiIntType Int@@ -93,8 +120,12 @@ | AbiStringType | AbiArrayDynamicType AbiType | AbiArrayType Int AbiType- deriving (Show, Read, Eq, Ord, Generic)+ | AbiTupleType (Vector AbiType)+ deriving (Read, Eq, Ord, Generic) +instance Show AbiType where+ show = Text.unpack . abiTypeSolidity+ data AbiKind = Dynamic | Static deriving (Show, Read, Eq, Ord, Generic) @@ -111,6 +142,7 @@ AbiStringType -> Dynamic AbiArrayDynamicType _ -> Dynamic AbiArrayType _ t -> abiKind t+ AbiTupleType ts -> if Dynamic `elem` (abiKind <$> ts) then Dynamic else Static _ -> Static abiValueType :: AbiValue -> AbiType@@ -124,6 +156,7 @@ AbiString _ -> AbiStringType AbiArrayDynamic t _ -> AbiArrayDynamicType t AbiArray n t _ -> AbiArrayType n t+ AbiTuple v -> AbiTupleType (abiValueType <$> v) abiTypeSolidity :: AbiType -> Text abiTypeSolidity = \case@@ -136,6 +169,7 @@ AbiStringType -> "string" AbiArrayDynamicType t -> abiTypeSolidity t <> "[]" AbiArrayType n t -> abiTypeSolidity t <> "[" <> pack (show n) <> "]"+ AbiTupleType ts -> "(" <> (Text.intercalate "," . Vector.toList $ abiTypeSolidity <$> ts) <> ")" getAbi :: AbiType -> Get AbiValue getAbi t = label (Text.unpack (abiTypeSolidity t)) $@@ -169,11 +203,13 @@ AbiArrayDynamic t' <$> label "array body" (getAbiSeq (fromIntegral n) (repeat t')) + AbiTupleType ts ->+ AbiTuple <$> getAbiSeq (Vector.length ts) (Vector.toList ts)+ putAbi :: AbiValue -> Put putAbi = \case- AbiUInt n x -> do- let word32Count = div (roundTo256Bits n) 4- forM_ (reverse [0 .. word32Count - 1]) $ \i ->+ AbiUInt _ x ->+ forM_ (reverse [0 .. 7]) $ \i -> putWord32be (fromIntegral (shiftR x (i * 32) .&. 0xffffffff)) AbiInt n x -> putAbi (AbiUInt n (fromIntegral x))@@ -182,7 +218,7 @@ AbiBytes n xs -> do forM_ [0 .. n-1] (putWord8 . BS.index xs)- replicateM_ (roundTo256Bits n - n) (putWord8 0)+ replicateM_ (roundTo32Bytes n - n) (putWord8 0) AbiBytesDynamic xs -> do let n = BS.length xs@@ -199,6 +235,9 @@ putAbi (AbiUInt 256 (fromIntegral (Vector.length xs))) putAbiSeq xs + AbiTuple v ->+ putAbiSeq v+ getAbiSeq :: Int -> [AbiType] -> Get (Vector AbiValue) getAbiSeq n ts = label "sequence" $ do hs <- label "sequence head" (getAbiHead n ts)@@ -209,7 +248,7 @@ -> Get [Either AbiType AbiValue] getAbiHead 0 _ = pure [] getAbiHead _ [] = fail "ran out of types"-getAbiHead n (t:ts) = do+getAbiHead n (t:ts) = case abiKind t of Dynamic -> (Left t :) <$> (skip 32 *> getAbiHead (n - 1) ts)@@ -227,17 +266,18 @@ abiValueSize :: AbiValue -> Int abiValueSize x = case x of- AbiUInt n _ -> roundTo256Bits n- AbiInt n _ -> roundTo256Bits n- AbiBytes n _ -> roundTo256Bits n+ AbiUInt _ _ -> 32+ AbiInt _ _ -> 32+ AbiBytes n _ -> roundTo32Bytes n AbiAddress _ -> 32 AbiBool _ -> 32 AbiArray _ _ xs -> Vector.sum (Vector.map abiHeadSize xs) + Vector.sum (Vector.map abiTailSize xs)- AbiBytesDynamic xs -> 32 + roundTo256Bits (BS.length xs)+ AbiBytesDynamic xs -> 32 + roundTo32Bytes (BS.length xs) AbiArrayDynamic _ xs -> 32 + Vector.sum (Vector.map abiHeadSize xs) + Vector.sum (Vector.map abiTailSize xs)- AbiString s -> 32 + roundTo256Bits (BS.length s)+ AbiString s -> 32 + roundTo32Bytes (BS.length s)+ AbiTuple v -> sum (abiValueSize <$> v) abiTailSize :: AbiValue -> Int abiTailSize x =@@ -245,11 +285,15 @@ Static -> 0 Dynamic -> case x of- AbiString s -> 32 + roundTo256Bits (BS.length s)- AbiBytesDynamic s -> 32 + roundTo256Bits (BS.length s)+ AbiString s -> 32 + roundTo32Bytes (BS.length s)+ AbiBytesDynamic s -> 32 + roundTo32Bytes (BS.length s) AbiArrayDynamic _ xs -> 32 + Vector.sum (Vector.map abiValueSize xs) AbiArray _ _ xs -> Vector.sum (Vector.map abiValueSize xs)+ AbiTuple v -> sum (headSize <$> v) + sum (abiTailSize <$> v) _ -> error "impossible"+ where headSize y = if abiKind (abiValueType y) == Static+ then abiValueSize y+ else 32 abiHeadSize :: AbiValue -> Int abiHeadSize x =@@ -257,9 +301,9 @@ Dynamic -> 32 Static -> case x of- AbiUInt n _ -> roundTo256Bits n- AbiInt n _ -> roundTo256Bits n- AbiBytes n _ -> roundTo256Bits n+ AbiUInt _ _ -> 32+ AbiInt _ _ -> 32+ AbiBytes n _ -> roundTo32Bytes n AbiAddress _ -> 32 AbiBool _ -> 32 AbiArray _ _ xs -> Vector.sum (Vector.map abiHeadSize xs) +@@ -267,6 +311,8 @@ AbiBytesDynamic _ -> 32 AbiArrayDynamic _ _ -> 32 AbiString _ -> 32+ AbiTuple v -> sum (abiHeadSize <$> v) ++ sum (abiTailSize <$> v) putAbiSeq :: Vector AbiValue -> Put putAbiSeq xs =@@ -283,17 +329,28 @@ encodeAbiValue :: AbiValue -> BS.ByteString encodeAbiValue = BSLazy.toStrict . runPut . putAbi +decodeAbiValue :: AbiType -> BSLazy.ByteString -> AbiValue+decodeAbiValue = runGet . getAbi++selector :: Text -> BS.ByteString+selector s = BSLazy.toStrict . runPut $ putWord32be (abiKeccak (encodeUtf8 s))++abiMethod :: Text -> AbiValue -> BS.ByteString+abiMethod s args = BSLazy.toStrict . runPut $ do+ putWord32be (abiKeccak (encodeUtf8 s))+ putAbi args+ abiCalldata :: Text -> Vector AbiValue -> BS.ByteString abiCalldata s xs = BSLazy.toStrict . runPut $ do putWord32be (abiKeccak (encodeUtf8 s)) putAbiSeq xs -parseTypeName :: Text -> Maybe AbiType-parseTypeName = P.parseMaybe typeWithArraySuffix+parseTypeName :: Vector AbiType -> Text -> Maybe AbiType+parseTypeName = P.parseMaybe . typeWithArraySuffix -typeWithArraySuffix :: P.Parsec () Text AbiType-typeWithArraySuffix = do- base <- basicType+typeWithArraySuffix :: Vector AbiType -> P.Parsec () Text AbiType+typeWithArraySuffix v = do+ base <- basicType v sizes <- P.many $ P.between@@ -301,24 +358,25 @@ (P.many P.digitChar) let- parseSize :: AbiType -> [Char] -> AbiType+ parseSize :: AbiType -> String -> AbiType parseSize t "" = AbiArrayDynamicType t parseSize t s = AbiArrayType (read s) t pure (foldl parseSize base sizes) -basicType :: P.Parsec () Text AbiType-basicType =+basicType :: Vector AbiType -> P.Parsec () Text AbiType+basicType v = P.choice- [ P.string "address" *> pure AbiAddressType- , P.string "bool" *> pure AbiBoolType- , P.string "string" *> pure AbiStringType+ [ P.string "address" $> AbiAddressType+ , P.string "bool" $> AbiBoolType+ , P.string "string" $> AbiStringType , sizedType "uint" AbiUIntType , sizedType "int" AbiIntType , sizedType "bytes" AbiBytesType - , P.string "bytes" *> pure AbiBytesDynamicType+ , P.string "bytes" $> AbiBytesDynamicType+ , P.string "tuple" $> AbiTupleType v ] where@@ -332,24 +390,22 @@ sum [ shiftL x ((n - i) * 32) | (x, i) <- zip (map fromIntegral xs) [1..] ] -pack8 :: Int -> [Word8] -> Word256-pack8 n xs =- sum [ shiftL x ((n - i) * 8)- | (x, i) <- zip (map fromIntegral xs) [1..] ]- asUInt :: Integral i => Int -> (i -> a) -> Get a asUInt n f = (\(AbiUInt _ x) -> f (fromIntegral x)) <$> getAbi (AbiUIntType n) getWord256 :: Get Word256 getWord256 = pack32 8 <$> replicateM 8 getWord32be -roundTo256Bits :: Integral a => a -> a-roundTo256Bits n = 32 * div (n + 255) 256+roundTo32Bytes :: Integral a => a -> a+roundTo32Bytes n = 32 * div (n + 31) 32 +emptyAbi :: AbiValue+emptyAbi = AbiTuple mempty+ getBytesWith256BitPadding :: Integral a => a -> Get ByteString getBytesWith256BitPadding i = (BS.pack <$> replicateM n getWord8)- <* skip ((roundTo256Bits n) - n)+ <* skip ((roundTo32Bytes n) - n) where n = fromIntegral i -- QuickCheck instances@@ -358,9 +414,9 @@ genAbiValue = \case AbiUIntType n -> genUInt n AbiIntType n ->- do AbiUInt _ x <- genUInt n- b <- arbitrary- pure $ AbiInt n (signedWord x * if b then 1 else -1)+ do a <- genUInt n+ let AbiUInt _ x = a+ pure $ AbiInt n (signedWord x) AbiAddressType -> (\(AbiUInt _ x) -> AbiAddress (fromIntegral x)) <$> genUInt 20 AbiBoolType ->@@ -378,14 +434,13 @@ AbiArrayType n t -> AbiArray n t . Vector.fromList <$> replicateM n (scale (`div` 2) (genAbiValue t))+ AbiTupleType ts ->+ AbiTuple <$> mapM genAbiValue ts where- genUInt n =- do x <- pack8 (div n 8) <$> replicateM n arbitrary- pure . AbiUInt n $- if n == 256 then x else mod x (2 ^ n)+ genUInt n = AbiUInt n <$> arbitraryIntegralWithMax n instance Arbitrary AbiType where- arbitrary = oneof+ arbitrary = sized $ \n -> oneof $ -- prevent empty tuples [ (AbiUIntType . (* 8)) <$> choose (1, 32) , (AbiIntType . (* 8)) <$> choose (1, 32) , pure AbiAddressType@@ -397,7 +452,10 @@ , AbiArrayType <$> (getPositive <$> arbitrary) <*> scale (`div` 2) arbitrary- ]+ ] <>+ [AbiTupleType+ <$> scale (`div` 2) (Vector.fromList <$> arbitrary)+ | n /= 0] instance Arbitrary AbiValue where arbitrary = arbitrary >>= genAbiValue@@ -406,8 +464,83 @@ Vector.toList v ++ map (AbiArrayDynamic t . Vector.fromList) (shrinkList shrink (Vector.toList v))+ AbiBytesDynamic b -> AbiBytesDynamic . BS.pack <$> shrinkList shrinkIntegral (BS.unpack b)+ AbiString b -> AbiString . BS.pack <$> shrinkList shrinkIntegral (BS.unpack b)+ AbiBytes n a | n <= 32 -> shrink $ AbiUInt (n * 8) (word256 a)+ --bytesN for N > 32 don't really exist right now anyway..+ AbiBytes _ _ | otherwise -> [] AbiArray _ t v -> Vector.toList v ++ map (\x -> AbiArray (length x) t (Vector.fromList x)) (shrinkList shrink (Vector.toList v))- _ -> []+ AbiTuple v -> Vector.toList $ AbiTuple . Vector.fromList . shrink <$> v+ AbiUInt n a -> AbiUInt n <$> (shrinkIntegral a)+ AbiInt n a -> AbiInt n <$> (shrinkIntegral a)+ AbiBool b -> AbiBool <$> shrink b+ AbiAddress a -> [AbiAddress 0xacab, AbiAddress 0xdeadbeef, AbiAddress 0xbabeface]+ <> (AbiAddress <$> shrinkIntegral a)+++-- Bool synonym with custom read instance+-- to be able to parse lower case 'false' and 'true'+data Boolz = Boolz Bool++instance Read Boolz where+ readsPrec _ ('T':'r':'u':'e':x) = [(Boolz True, x)]+ readsPrec _ ('t':'r':'u':'e':x) = [(Boolz True, x)]+ readsPrec _ ('f':'a':'l':'s':'e':x) = [(Boolz False, x)]+ readsPrec _ ('F':'a':'l':'s':'e':x) = [(Boolz False, x)]+ readsPrec _ [] = []+ readsPrec n (_:t) = readsPrec n t++makeAbiValue :: AbiType -> String -> AbiValue+makeAbiValue typ str = case readP_to_S (parseAbiValue typ) str of+ [] -> error "could not parse abi arguments"+ ((val,_):_) -> val++parseAbiValue :: AbiType -> ReadP AbiValue+parseAbiValue (AbiUIntType n) = do W256 w256 <- readS_to_P reads+ return $ AbiUInt n w256+parseAbiValue (AbiIntType n) = do W256 w256 <- readS_to_P reads+ return $ AbiInt n (num w256)+parseAbiValue AbiAddressType = AbiAddress <$> readS_to_P reads+parseAbiValue AbiBoolType = (do W256 w256 <- readS_to_P reads+ return $ AbiBool (w256 /= 0))+ <|> (do Boolz b <- readS_to_P reads+ return $ AbiBool b)+parseAbiValue (AbiBytesType n) = AbiBytes n <$> do ByteStringS bytes <- readS_to_P reads+ return bytes+parseAbiValue AbiBytesDynamicType = AbiBytesDynamic <$> do ByteStringS bytes <- readS_to_P reads+ return bytes+parseAbiValue AbiStringType = AbiString <$> do Char8.pack <$> readS_to_P reads+parseAbiValue (AbiArrayDynamicType typ) =+ AbiArrayDynamic typ <$> do a <- listP (parseAbiValue typ)+ return $ Vector.fromList a+parseAbiValue (AbiArrayType n typ) =+ AbiArray n typ <$> do a <- listP (parseAbiValue typ)+ return $ Vector.fromList a+parseAbiValue (AbiTupleType _) = error "tuple types not supported"++listP :: ReadP a -> ReadP [a]+listP parser = between (char '[') (char ']') ((do skipSpaces+ a <- parser+ skipSpaces+ return a) `sepBy` (char ','))+++-- A modification of 'arbitrarySizedBoundedIntegral' quickcheck library+-- which takes the maxbound explicitly rather than relying on a Bounded instance.+-- Essentially a mix between three types of generators:+-- one that strongly prefers values close to 0, one that prefers values close to max+-- and one that chooses uniformly.+arbitraryIntegralWithMax :: (Integral a) => Int -> Gen a+arbitraryIntegralWithMax maxbound =+ sized $ \s ->+ do let mn = 0 :: Int+ mx = maxbound+ bits n | n `quot` 2 == 0 = 0+ | otherwise = 1 + bits (n `quot` 2)+ k = 2^(s*(bits mn `max` bits mx `max` 40) `div` 100)+ smol <- choose (toInteger mn `max` (-k), toInteger mx `min` k)+ mid <- choose (0, maxbound)+ elements [fromIntegral smol, fromIntegral mid, fromIntegral (maxbound - (fromIntegral smol))]
@@ -1,19 +1,17 @@ {-# Language FlexibleInstances #-}-{-# Language StandaloneDeriving #-} {-# Language StrictData #-} module EVM.Concrete where -import Prelude hiding (Word, (^))+import Prelude hiding (Word) -import EVM.Types (W256 (..), num, toWord512, fromWord512)-import EVM.Types (word, padRight, byteAt) import EVM.Keccak (keccak)+import EVM.RLP+import EVM.Types (Addr, W256 (..), num, word, padRight, word160Bytes, word256Bytes) import Control.Lens ((^?), ix)-import Data.Bits (Bits (..), FiniteBits (..))+import Data.Bits (Bits (..), FiniteBits (..), shiftL, shiftR) import Data.ByteString (ByteString)-import Data.DoubleWord (signedWord, unsignedWord) import Data.Maybe (fromMaybe) import Data.Semigroup ((<>)) import Data.Word (Word8)@@ -24,9 +22,6 @@ wordAt i bs = word (padRight 32 (BS.drop i bs)) -word256Bytes :: W256 -> ByteString-word256Bytes x = BS.pack [byteAt x (31 - i) | i <- [0..31]]- readByteOrZero :: Int -> ByteString -> Word8 readByteOrZero i bs = fromMaybe 0 (bs ^? ix i) @@ -34,84 +29,49 @@ byteStringSliceWithDefaultZeroes offset size bs = if size == 0 then ""+ -- else if offset > BS.length bs+ -- then BS.replicate size 0+ -- todo: this ^^ should work, investigate why it causes more GST fails else let bs' = BS.take size (BS.drop offset bs) in bs' <> BS.replicate (size - BS.length bs') 0 -data Whiff = Dull | FromKeccak ByteString+-- | This type can give insight into the provenance of a term+data Whiff = Dull+ | FromKeccak ByteString+ | Var String+ | InfixBinOp String Whiff Whiff+ | BinOp String Whiff Whiff+ | UnOp String Whiff deriving Show w256 :: W256 -> Word w256 = C Dull -data Word = C Whiff W256--wordToByte :: Word -> Word8-wordToByte (C _ x) = num (x .&. 0xff)--exponentiate :: Word -> Word -> Word-exponentiate (C _ x) (C _ y) = w256 (x ^ y)--sdiv :: Word -> Word -> Word-sdiv _ (C _ (W256 0)) = 0-sdiv (C _ (W256 x)) (C _ (W256 y)) =- let sx = signedWord x- sy = signedWord y- in w256 . W256 . unsignedWord $ quot sx sy--smod :: Word -> Word -> Word-smod _ (C _ (W256 0)) = 0-smod (C _ (W256 x)) (C _ (W256 y)) =- let sx = signedWord x- sy = signedWord y- in w256 . W256 . unsignedWord $ rem sx sy--addmod :: Word -> Word -> Word -> Word-addmod _ _ (C _ (W256 0)) = 0-addmod (C _ x) (C _ y) (C _ z) =- w256 $- fromWord512- ((toWord512 x + toWord512 y) `mod` (toWord512 z))--mulmod :: Word -> Word -> Word -> Word-mulmod _ _ (C _ (W256 0)) = 0-mulmod (C _ x) (C _ y) (C _ z) =- w256 $- fromWord512- ((toWord512 x * toWord512 y) `mod` (toWord512 z))--slt :: Word -> Word -> Word-slt (C _ (W256 x)) (C _ (W256 y)) =- if signedWord x < signedWord y then w256 1 else w256 0--sgt :: Word -> Word -> Word-sgt (C _ (W256 x)) (C _ (W256 y)) =- if signedWord x > signedWord y then w256 1 else w256 0+data Word = C Whiff W256 --maybe to remove completely in the future wordValue :: Word -> W256 wordValue (C _ x) = x sliceMemory :: (Integral a, Integral b) => a -> b -> ByteString -> ByteString-sliceMemory o s m =- byteStringSliceWithDefaultZeroes (num o) (num s) m+sliceMemory o s =+ byteStringSliceWithDefaultZeroes (num o) (num s) writeMemory :: ByteString -> Word -> Word -> Word -> ByteString -> ByteString writeMemory bs1 (C _ n) (C _ src) (C _ dst) bs0 =- if src > num (BS.length bs1)- then- let- (a, b) = BS.splitAt (num dst) bs0- c = BS.replicate (num n) 0- b' = BS.drop (num n) b- in- a <> c <> b'- else- let- (a, b) = BS.splitAt (num dst) bs0- c = BS.take (num n) (BS.drop (num src) bs1)- b' = BS.drop (num n) b- in- a <> BS.replicate (num dst - BS.length a) 0 <> c <> b'+ let+ (a, b) = BS.splitAt (num dst) bs0+ a' = BS.replicate (num dst - BS.length a) 0+ -- sliceMemory should work for both cases, but we are using 256 bit+ -- words, whereas ByteString is only defined up to 64 bit. For large n,+ -- src, dst this will cause problems (often in GeneralStateTests).+ -- Later we could reimplement ByteString for 256 bit arguments.+ c = if src > num (BS.length bs1)+ then BS.replicate (num n) 0+ else sliceMemory src n bs1+ b' = BS.drop (num n) b+ in+ a <> a' <> c <> b' readMemoryWord :: Word -> ByteString -> Word readMemoryWord (C _ i) m =@@ -119,7 +79,7 @@ go !a (-1) = a go !a !n = go (a + shiftL (num $ readByteOrZero (num i + n) m) (8 * (31 - n))) (n - 1)- in {-# SCC readMemoryWord #-}+ in {-# SCC "readMemoryWord" #-} w256 $ go (0 :: W256) (31 :: Int) readMemoryWord32 :: Word -> ByteString -> Word@@ -128,16 +88,16 @@ go !a (-1) = a go !a !n = go (a + shiftL (num $ readByteOrZero (num i + n) m) (8 * (3 - n))) (n - 1)- in {-# SCC readMemoryWord32 #-}+ in {-# SCC "readMemoryWord32" #-} w256 $ go (0 :: W256) (3 :: Int) setMemoryWord :: Word -> Word -> ByteString -> ByteString-setMemoryWord (C _ i) (C _ x) m =- writeMemory (word256Bytes x) 32 0 (num i) m+setMemoryWord (C _ i) (C _ x) =+ writeMemory (word256Bytes x) 32 0 (num i) setMemoryByte :: Word -> Word8 -> ByteString -> ByteString-setMemoryByte (C _ i) x m =- writeMemory (BS.singleton x) 1 0 (num i) m+setMemoryByte (C _ i) x =+ writeMemory (BS.singleton x) 1 0 (num i) readBlobWord :: Word -> ByteString -> Word readBlobWord (C _ i) x =@@ -153,6 +113,10 @@ instance Show Word where show (C Dull x) = show x+ show (C (Var var) x) = var ++ ": " ++ show x+ show (C (InfixBinOp symbol x y) z) = show x ++ symbol ++ show y ++ ": " ++ show z+ show (C (BinOp symbol x y) z) = symbol ++ show x ++ show y ++ ": " ++ show z+ show (C (UnOp symbol x) z) = symbol ++ show x ++ ": " ++ show z show (C whiff x) = show whiff ++ ": " ++ show x instance Read Word where@@ -171,7 +135,7 @@ bitSize (C _ x) = bitSize x bitSizeMaybe (C _ x) = bitSizeMaybe x isSigned (C _ x) = isSigned x- testBit (C _ x) i = testBit x i+ testBit (C _ x) = testBit x bit i = w256 (bit i) popCount (C _ x) = popCount x @@ -225,3 +189,10 @@ g x y z | not (testBit y 0) = g (x * x) (y `shiftR` 1) z | y == 1 = x * z | otherwise = g (x * x) ((y - 1) `shiftR` 1) (x * z)++createAddress :: Addr -> W256 -> Addr+createAddress a n = num $ keccak $ rlpList [rlpWord160 a, rlpWord256 n]++create2Address :: Addr -> W256 -> ByteString -> Addr+create2Address a s b = num $ keccak $ mconcat+ [BS.singleton 0xff, word160Bytes a, word256Bytes $ num s, word256Bytes $ keccak b]
@@ -3,11 +3,11 @@ module EVM.Dapp where import EVM (Trace, traceCodehash, traceOpIx)-import EVM.ABI (Event)+import EVM.ABI (Event, AbiType) import EVM.Debug (srcMapCodePos) import EVM.Keccak (abiKeccak) import EVM.Solidity (SolcContract, CodeType (..), SourceCache, SrcMap)-import EVM.Solidity (contractName)+import EVM.Solidity (contractName, methodInputs) import EVM.Solidity (runtimeCodehash, creationCodehash, abiMap) import EVM.Solidity (runtimeSrcmap, creationSrcmap, eventMap) import EVM.Solidity (methodSignature, contractAst, astIdMap, astSrcMap)@@ -19,8 +19,8 @@ import Data.Map (Map) import Data.Monoid ((<>)) import Data.Word (Word32)-import Data.List (sort) +import Control.Applicative ((<$>)) import Control.Arrow ((>>>)) import Control.Lens @@ -31,10 +31,10 @@ , _dappSolcByName :: Map Text SolcContract , _dappSolcByHash :: Map W256 (CodeType, SolcContract) , _dappSources :: SourceCache- , _dappUnitTests :: [(Text, [Text])]+ , _dappUnitTests :: [(Text, [(Text, [AbiType])])] , _dappEventMap :: Map W256 Event , _dappAstIdMap :: Map Int Value- , _dappAstSrcMap :: (SrcMap -> Maybe Value)+ , _dappAstSrcMap :: SrcMap -> Maybe Value } makeLenses ''DappInfo@@ -70,24 +70,23 @@ unitTestMarkerAbi :: Word32 unitTestMarkerAbi = abiKeccak (encodeUtf8 "IS_TEST()") -findUnitTests :: (Text -> Bool) -> ([SolcContract] -> [(Text, [Text])])+findUnitTests :: (Text -> Bool) -> ([SolcContract] -> [(Text, [(Text, [AbiType])])]) findUnitTests matcher = concatMap $ \c -> case preview (abiMap . ix unitTestMarkerAbi) c of Nothing -> [] Just _ ->- let testNames = (unitTestMethods matcher) c- in if null testNames- then []- else [(view contractName c, testNames)]+ let testNames = unitTestMethodsFiltered matcher c+ in ([(view contractName c, testNames) | not (null testNames)]) -unitTestMethods :: (Text -> Bool) -> (SolcContract -> [Text])-unitTestMethods matcher =- view abiMap- >>> Map.elems- >>> map (view methodSignature)- >>> filter matcher- >>> sort+unitTestMethodsFiltered :: (Text -> Bool) -> (SolcContract -> [(Text, [AbiType])])+unitTestMethodsFiltered matcher c = filter (matcher . fst) $ unitTestMethods c++unitTestMethods :: SolcContract -> [(Text, [AbiType])]+unitTestMethods = view abiMap+ >>> Map.elems+ >>> map (\f -> (view methodSignature f,+ snd <$> view methodInputs f)) traceSrcMap :: DappInfo -> Trace -> Maybe SrcMap traceSrcMap dapp trace =
@@ -1,9 +1,7 @@ module EVM.Debug where -import EVM (Contract, storage, nonce, balance)-import EVM (bytecode, codehash, bytecode)-import EVM.Solidity (SrcMap, srcMapFile, srcMapOffset, srcMapLength)-import EVM.Solidity (SourceCache, sourceFiles)+import EVM (Contract, storage, nonce, balance, bytecode, codehash)+import EVM.Solidity (SrcMap, srcMapFile, srcMapOffset, srcMapLength, SourceCache, sourceFiles) import EVM.Types (Addr) import Control.Arrow (second)@@ -29,7 +27,7 @@ prettyContract :: Contract -> Doc prettyContract c =- object $+ object [ (text "codesize", int (ByteString.length (c ^. bytecode))) , (text "codehash", text (show (c ^. codehash))) , (text "balance", int (fromIntegral (c ^. balance)))@@ -40,7 +38,7 @@ prettyContracts :: Map Addr Contract -> Doc prettyContracts x =- object $+ object (map (\(a, b) -> (text (show a), prettyContract b)) (Map.toList x))
@@ -0,0 +1,13 @@+module EVM.Demand (demand) where++import Control.DeepSeq (NFData, force)+import Control.Exception.Base (evaluate)+import Control.Monad.IO.Class (MonadIO, liftIO)++-- | This is an easy way to force full evaluation of a value inside of+-- the IO monad, being essentially just the composition of @evaluate@+-- and @force@.+demand :: (MonadIO m, NFData a) => a -> m ()+demand x = do+ _ <- liftIO (evaluate (force x))+ return ()
@@ -11,17 +11,21 @@ import qualified EVM.Emacs import qualified EVM.Facts as Facts import qualified EVM.Facts.Git as Git+import qualified EVM.Stepper+import qualified EVM.VMTest as VMTest +import Control.Monad.State.Strict (execStateT) import Data.Text (isPrefixOf) import qualified Data.Map as Map+import qualified Data.ByteString.Lazy as LazyByteString loadDappInfo :: String -> String -> IO DappInfo loadDappInfo path file = withCurrentDirectory path $ readSolc file >>= \case- Just (contractMap, cache) -> do+ Just (contractMap, cache) -> pure (dappInfo "." contractMap cache) _ -> error "nope, sorry"@@ -40,8 +44,11 @@ let opts = UnitTestOptions { oracle = EVM.Fetch.zero- , verbose = False+ , verbose = Nothing+ , maxIter = Nothing , match = ""+ , fuzzRuns = 100+ , replay = Nothing , vmModifier = loadFacts , testParams = params }@@ -53,6 +60,26 @@ Nothing -> error ("Failed to read Solidity JSON for `" ++ path ++ "'") +runBCTest :: (String, VMTest.Case) -> IO Bool+runBCTest (name, x) = do+ let vm0 = VMTest.vmForCase x+ putStr (name ++ " ")+ result <-+ execStateT (EVM.Stepper.interpret EVM.Fetch.zero EVM.Stepper.execFully) vm0+ ok <- VMTest.checkExpectation False x result+ putStrLn (if ok then "ok" else "")+ return ok++ghciBCTest :: String -> IO ()+ghciBCTest file = do+ let parser = VMTest.parseBCSuite+ parsed <- parser <$> LazyByteString.readFile file+ case parsed of+ Left "No cases to check." -> putStrLn "no-cases ok"+ Left err -> print err+ Right allTests ->+ mapM_ runBCTest (Map.toList allTests)+ ghciTty :: String -> String -> Maybe String -> IO () ghciTty root path state = withCurrentDirectory root $ do@@ -67,8 +94,10 @@ let testOpts = UnitTestOptions { oracle = EVM.Fetch.zero- , verbose = False+ , verbose = Nothing , match = ""+ , fuzzRuns = 100+ , replay = Nothing , vmModifier = loadFacts , testParams = params }
@@ -1,5 +1,6 @@ {-# Language ImplicitParams #-} {-# Language TemplateHaskell #-}+{-# Language DataKinds #-} {-# Language FlexibleInstances #-} module EVM.Emacs where@@ -15,22 +16,30 @@ import Data.SCargot.Language.HaskLike import Data.SCargot.Repr import Data.SCargot.Repr.Basic+import Data.Set (Set) import Data.Text (Text, pack, unpack)+import Data.SBV hiding (Word, output) import EVM+import EVM.ABI import EVM.Concrete+import EVM.Symbolic import EVM.Dapp+import EVM.Debug (srcMapCodePos) import EVM.Fetch (Fetcher)+import EVM.Op import EVM.Solidity import EVM.Stepper (Stepper) import EVM.TTY (currentSrcMap) import EVM.Types-import EVM.UnitTest hiding (interpret)+import EVM.UnitTest import Prelude hiding (Word) import System.Directory import System.IO import qualified Control.Monad.Operational as Operational-import qualified Data.ByteString as BS+import qualified Data.List as List import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.Vector as Vector import qualified EVM.Fetch as Fetch import qualified EVM.Stepper as Stepper @@ -43,6 +52,7 @@ , _uiVmFirstState :: UiVmState , _uiVmFetcher :: Fetcher , _uiVmMessage :: Maybe Text+ , _uiVmSentHashes :: Set W256 } makeLenses ''UiVmState@@ -105,12 +115,12 @@ -- but stopping before the next instruction. interpret StepNone (k r) - StepMany 0 -> do+ StepMany 0 -> -- Finish the continuation until the next instruction; -- then, pause & await user. interpret StepNone restart - StepMany i -> do+ StepMany i -> -- Run one instruction. interpret StepOne restart >>= \case@@ -155,17 +165,9 @@ vm0 <- use uiVm let (r, vm1) = runState m vm0 modify (flip updateUiVmState vm1)+ modify updateSentHashes interpret mode (k r) - -- Stepper wants to emit a message.- Stepper.Note s -> do- assign uiVmMessage (Just s)- interpret mode (k ())-- -- Stepper wants to exit because of a failure.- Stepper.Fail e ->- error ("VM error: " ++ show e)- stepOneOpcode :: UiVmState -> UiVmState stepOneOpcode ui = let@@ -178,6 +180,11 @@ updateUiVmState ui vm = ui & set uiVm vm +updateSentHashes :: UiVmState -> UiVmState+updateSentHashes ui =+ let sent = allHashes (view uiVm ui) in+ ui & set uiVmSentHashes sent+ type Sexp = WellFormedSExpr HaskLikeAtom prompt :: Console (Maybe Sexp)@@ -251,35 +258,77 @@ _ -> output (L [A ("unrecognized-command" :: Text)]) -handleCmd (UiDappLoaded dapp) = \case+handleCmd (UiDappLoaded _) = \case ("run-test", [WFSAtom (HSString contractPath), WFSAtom (HSString testName)]) -> do opts <- defaultUnitTestOptions- put (UiVm (initialStateForTest opts dapp (contractPath, testName)))+ put (UiVm (initialStateForTest opts (contractPath, testName))) outputVm _ -> output (L [A ("unrecognized-command" :: Text)]) handleCmd (UiVm s) = \case- ("step", [WFSAtom (HSString modeName)]) -> do+ ("step", [WFSAtom (HSString modeName)]) -> case parseStepMode s modeName of Just mode -> do takeStep s StepNormally mode outputVm Nothing -> output (L [A ("unrecognized-command" :: Text)])+ ("step", [WFSList [ WFSAtom (HSString "file-line")+ , WFSAtom (HSString fileName)+ , WFSAtom (HSInt (fromIntegral -> lineNumber))+ ]]) ->+ case view uiVmDapp s of+ Nothing ->+ output (L [A ("impossible" :: Text)])+ Just dapp -> do+ takeStep s StepNormally+ (StepUntil (atFileLine dapp fileName lineNumber))+ outputVm _ -> output (L [A ("unrecognized-command" :: Text)]) +atFileLine :: DappInfo -> Text -> Int -> VM -> Bool+atFileLine dapp wantedFileName wantedLineNumber vm =+ case currentSrcMap dapp vm of+ Nothing -> False+ Just sm ->+ case view (dappSources . sourceFiles . at (srcMapFile sm)) dapp of+ Nothing -> False+ Just _ ->+ let+ (currentFileName, currentLineNumber) =+ fromJust (srcMapCodePos (view dappSources dapp) sm)+ in+ currentFileName == wantedFileName &&+ currentLineNumber == wantedLineNumber++codeByHash :: W256 -> VM -> Maybe ByteString+codeByHash h vm = do+ let cs = view (env . contracts) vm+ c <- List.find (\c -> h == (view codehash c)) (Map.elems cs)+ return (view bytecode c)++allHashes :: VM -> Set W256+allHashes vm = let cs = view (env . contracts) vm+ in Set.fromList ((view codehash) <$> Map.elems cs)++prettifyCode :: ByteString -> String+prettifyCode b = List.intercalate "\n" (opString <$> (Vector.toList (EVM.mkCodeOps b)))+ outputVm :: Console () outputVm = do UiVm s <- get- let- noMap =- output $+ let vm = view uiVm s+ sendHashes = Set.difference (allHashes vm) (view uiVmSentHashes s)+ sendCodes = Map.fromSet (`codeByHash` vm) sendHashes+ noMap =+ output $ L [ A "step"- , L [A ("pc" :: Text), A (txt (view (uiVm . state . pc) s))]]-+ , L [A ("vm" :: Text), sexp (view uiVm s)]+ , L [A ("newCodes" :: Text), sexp ((fmap prettifyCode) <$> sendCodes)]+ ] fromMaybe noMap $ do dapp <- view uiVmDapp s sm <- currentSrcMap dapp (view uiVm s)@@ -293,6 +342,7 @@ , A (txt (srcMapLength sm)) , A (txt (srcMapJump sm)) ]+ , L [A ("newCodes" :: Text), sexp ((fmap prettifyCode) <$> sendCodes)] ] @@ -328,7 +378,7 @@ case runState m ui of - (Stepped stepper, ui') -> do+ (Stepped stepper, ui') -> put (UiVm (ui' & set uiVmNextStep stepper)) (Blocked blocker, ui') ->@@ -369,6 +419,10 @@ instance SDisplay (SExpr Text) where sexp = id +instance SDisplay Storage where+ sexp (Symbolic _) = error "idk"+ sexp (Concrete d) = sexp d+ instance SDisplay VM where sexp x = L [ L [A "result", sexp (view result x)]@@ -381,7 +435,7 @@ quoted x = "\"" <> x <> "\"" instance SDisplay Addr where- sexp = A . quoted . pack . showAddrWith0x+ sexp = A . quoted . pack . show instance SDisplay Contract where sexp x =@@ -394,6 +448,23 @@ instance SDisplay W256 where sexp x = A (txt (txt x)) +-- no idea what's going on here+instance SDisplay (SWord 256) where+ sexp x = A (txt (txt x))++-- no idea what's going on here+instance SDisplay (SymWord) where+ sexp x = A (txt (txt x))++-- no idea what's going on here+instance SDisplay (SWord 8) where+ sexp x = A (txt (txt x))++-- no idea what's going on here+instance SDisplay Buffer where+ sexp (SymbolicBuffer x) = sexp x+ sexp (ConcreteBuffer x) = sexp x+ instance (SDisplay k, SDisplay v) => SDisplay (Map k v) where sexp x = L [L [sexp k, sexp v] | (k, v) <- Map.toList x] @@ -425,18 +496,22 @@ instance SDisplay a => SDisplay [a] where sexp = L . map sexp +-- this overlaps the neighbouring [a] instance+instance {-# OVERLAPPING #-} SDisplay String where+ sexp x = A (txt x)+ instance SDisplay Word where- sexp (C Dull x) = A (quoted (txt x)) sexp (C (FromKeccak bs) x) = L [A "hash", A (txt x), sexp bs]+ sexp (C _ x) = A (quoted (txt x)) instance SDisplay ByteString where- sexp = A . txt . pack . showByteStringWith0x+ sexp = A . txt . pack . show . ByteStringS -sexpMemory :: ByteString -> SExpr Text+sexpMemory :: Buffer -> SExpr Text sexpMemory bs =- if BS.length bs > 1024- then L [A "large-memory", A (txt (BS.length bs))]+ if len bs > 1024+ then L [A "large-memory", A (txt (len bs))] else sexp bs defaultUnitTestOptions :: MonadIO m => m UnitTestOptions@@ -444,39 +519,41 @@ params <- liftIO getParametersFromEnvironmentVariables pure UnitTestOptions { oracle = Fetch.zero- , verbose = False+ , verbose = Nothing+ , maxIter = Nothing , match = ""+ , fuzzRuns = 100+ , replay = Nothing , vmModifier = id , testParams = params } initialStateForTest :: UnitTestOptions- -> DappInfo -> (Text, Text) -> UiVmState-initialStateForTest opts@(UnitTestOptions {..}) dapp (contractPath, testName) =+initialStateForTest opts@(UnitTestOptions {..}) (contractPath, testName) = ui1 where script = do Stepper.evm . pushTrace . EntryTrace $ "test " <> testName <> " (" <> contractPath <> ")" initializeUnitTest opts- void (runUnitTest opts testName)+ void (runUnitTest opts testName (AbiTuple mempty)) ui0 = UiVmState { _uiVm = vm0 , _uiVmNextStep = script , _uiVmSolc = Just testContract- , _uiVmDapp = Just dapp , _uiVmStepCount = 0 , _uiVmFirstState = undefined , _uiVmFetcher = oracle , _uiVmMessage = Nothing+ , _uiVmSentHashes = Set.empty } Just testContract = view (dappSolcByName . at contractPath) dapp vm0 =- initialUnitTestVm opts testContract (Map.elems (view dappSolcByName dapp))+ initialUnitTestVm opts testContract ui1 = updateUiVmState ui0 vm0 & set uiVmFirstState ui1
@@ -1,7 +1,8 @@ module EVM.Exec where import EVM-import EVM.Keccak (newContractAddress)+import EVM.Concrete (createAddress)+import EVM.Symbolic (litAddr) import EVM.Types import qualified EVM.FeeSchedule as FeeSchedule@@ -20,28 +21,39 @@ vmForEthrunCreation :: ByteString -> VM vmForEthrunCreation creationCode = (makeVm $ VMOpts- { vmoptCode = creationCode- , vmoptCalldata = ""+ { vmoptContract = initialContract (InitCode creationCode)+ , vmoptCalldata = (mempty, 0) , vmoptValue = 0- , vmoptAddress = newContractAddress ethrunAddress 1- , vmoptCaller = ethrunAddress+ , vmoptAddress = createAddress ethrunAddress 1+ , vmoptCaller = litAddr ethrunAddress , vmoptOrigin = ethrunAddress , vmoptCoinbase = 0 , vmoptNumber = 0 , vmoptTimestamp = 0- , vmoptGaslimit = 0+ , vmoptBlockGaslimit = 0 , vmoptGasprice = 0 , vmoptDifficulty = 0 , vmoptGas = 0xffffffffffffffff- , vmoptSchedule = FeeSchedule.metropolis+ , vmoptGaslimit = 0xffffffffffffffff+ , vmoptMaxCodeSize = 0xffffffff+ , vmoptSchedule = FeeSchedule.istanbul+ , vmoptChainId = 1+ , vmoptCreate = False+ , vmoptStorageModel = ConcreteS }) & set (env . contracts . at ethrunAddress)- (Just (initialContract mempty))+ (Just (initialContract (RuntimeCode mempty))) exec :: MonadState VM m => m VMResult exec = use EVM.result >>= \case Nothing -> State.state (runState exec1) >> exec Just x -> return x++run :: MonadState VM m => m VM+run =+ use EVM.result >>= \case+ Nothing -> State.state (runState exec1) >> run+ Just _ -> State.get execWhile :: MonadState VM m => (VM -> Bool) -> m Int execWhile p = go 0
@@ -1,7 +1,6 @@ {-# Language PartialTypeSignatures #-} {-# Language FlexibleInstances #-} {-# Language ExtendedDefaultRules #-}-{-# Language NamedFieldPuns #-} {-# Language PatternSynonyms #-} {-# Language RecordWildCards #-} {-# Language ScopedTypeVariables #-}@@ -36,14 +35,16 @@ import EVM (VM, Contract) import EVM.Concrete (Word)-import EVM (balance, nonce, storage, bytecode, env, contracts)+import EVM.Symbolic (litWord, SymWord, forceLit)+import EVM (balance, nonce, storage, bytecode, env, contracts, contract, state) import EVM.Types (Addr) -import qualified EVM as EVM+import qualified EVM import Prelude hiding (Word) -import Control.Lens (view, set, at, ix, (&))+import Control.Lens (view, set, at, ix, (&), over, assign)+import Control.Monad.State.Strict (execState, when) import Data.ByteString (ByteString) import Data.Monoid ((<>)) import Data.Ord (comparing)@@ -117,12 +118,14 @@ ] storageFacts :: Addr -> Contract -> [Fact]-storageFacts a x = map f (Map.toList (view storage x))+storageFacts a x = case view storage x of+ EVM.Symbolic _ -> []+ EVM.Concrete s -> map f (Map.toList s) where- f :: (Word, Word) -> Fact+ f :: (Word, SymWord) -> Fact f (k, v) = StorageFact { addr = a- , what = fromIntegral v+ , what = fromIntegral (forceLit v) , which = fromIntegral k } @@ -141,10 +144,11 @@ apply1 :: VM -> Fact -> VM apply1 vm fact = case fact of- CodeFact {..} ->- vm & set (env . contracts . at addr) (Just (EVM.initialContract blob))+ CodeFact {..} -> flip execState vm $ do+ assign (env . contracts . at addr) (Just (EVM.initialContract (EVM.RuntimeCode blob)))+ when (view (state . contract) vm == addr) $ EVM.loadContract addr StorageFact {..} ->- vm & set (env . contracts . ix addr . storage . at which) (Just what)+ vm & over (env . contracts . ix addr . storage) (EVM.writeStorage (litWord which) (litWord what)) BalanceFact {..} -> vm & set (env . contracts . ix addr . balance) what NonceFact {..} ->@@ -162,9 +166,9 @@ -- Applies a set of facts to a VM. apply :: VM -> Set Fact -> VM-apply vm =+apply = -- The set's ordering is relevant; see `apply1`.- foldl apply1 vm+ foldl apply1 factToFile :: Fact -> File factToFile fact = case fact of
@@ -1,4 +1,3 @@-{-# Language ViewPatterns #-} -- This is a backend for the fact representation that uses a Git -- repository as the store.@@ -9,8 +8,7 @@ , RepoAt (..) ) where -import EVM.Facts (Fact (..), File (..), Path (..), Data (..))-import EVM.Facts (fileToFact, factToFile)+import EVM.Facts (Fact (..), File (..), Path (..), Data (..), fileToFact, factToFile) import Control.Lens import Data.Set (Set)@@ -30,11 +28,11 @@ where f :: File -> Git.File f (File (Path ps p) (Data x)) =- (Git.File (Git.Path ps p) x)+ Git.File (Git.Path ps p) x g :: Git.File -> File g (Git.File (Git.Path ps p) x) =- (File (Path ps p) (Data x))+ File (Path ps p) (Data x) saveFacts :: RepoAt -> Set Fact -> IO () saveFacts (RepoAt repo) facts =
@@ -1,6 +1,6 @@ module EVM.FeeSchedule where -data Num n => FeeSchedule n = FeeSchedule+data FeeSchedule n = FeeSchedule { g_zero :: n , g_base :: n , g_verylow :: n@@ -14,8 +14,9 @@ , g_sset :: n , g_sreset :: n , r_sclear :: n+ , g_selfdestruct :: n+ , g_selfdestruct_newaccount :: n , r_selfdestruct :: n- , r_selfdestruct_newaccount :: n , g_create :: n , g_codedeposit :: n , g_call :: n@@ -36,6 +37,14 @@ , g_sha3word :: n , g_copy :: n , g_blockhash :: n+ , g_extcodehash :: n+ , g_quaddivisor :: n+ , g_ecadd :: n+ , g_ecmul :: n+ , g_pairing_point :: n+ , g_pairing_base :: n+ , g_fround :: n+ , r_block :: n } deriving Show -- For the purposes of this module, we define an EIP as just a fee@@ -50,8 +59,8 @@ , g_balance = 400 , g_sload = 200 , g_call = 700- , r_selfdestruct = 5000- , r_selfdestruct_newaccount = 25000+ , g_selfdestruct = 5000+ , g_selfdestruct_newaccount = 25000 } -- EIP160: EXP cost increase@@ -75,8 +84,9 @@ , g_sset = 20000 , g_sreset = 5000 , r_sclear = 15000- , r_selfdestruct = 0- , r_selfdestruct_newaccount = 0+ , g_selfdestruct = 0+ , g_selfdestruct_newaccount = 0+ , r_selfdestruct = 24000 , g_create = 32000 , g_codedeposit = 200 , g_call = 40@@ -97,7 +107,54 @@ , g_sha3word = 6 , g_copy = 3 , g_blockhash = 20+ , g_extcodehash = 400+ , g_quaddivisor = 20+ , g_ecadd = 500+ , g_ecmul = 40000+ , g_pairing_point = 80000+ , g_pairing_base = 100000+ , g_fround = 1+ , r_block = 2000000000000000000 } metropolis :: Num n => FeeSchedule n metropolis = eip160 . eip150 $ homestead++-- EIP1108: Reduce alt_bn128 precompile gas costs+-- <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1108.md>+eip1108 :: EIP n+eip1108 fees = fees+ { g_ecadd = 150+ , g_ecmul = 6000+ , g_pairing_point = 34000+ , g_pairing_base = 45000+ }++-- EIP1884: Repricing for trie-size-dependent opcodes+-- <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1884.md>+eip1884 :: EIP n+eip1884 fees = fees+ { g_sload = 800+ , g_balance = 700+ , g_extcodehash = 700+ }++-- EIP2028: Transaction data gas cost reduction+-- <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2028.md>+eip2028 :: EIP n+eip2028 fees = fees+ { g_txdatanonzero = 16+ }++-- EIP2200: Structured definitions for gas metering+-- <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2200.md>+eip2200 :: EIP n+eip2200 fees = fees+ { g_sload = 800+ , g_sset = 20000 -- not changed+ , g_sreset = 5000 -- not changed+ , r_sclear = 15000 -- not changed+ }++istanbul :: Num n => FeeSchedule n+istanbul = eip1108 . eip1884 . eip2028 . eip2200 $ metropolis
@@ -5,14 +5,18 @@ import Prelude hiding (Word) -import EVM.Types (Addr, W256, showAddrWith0x, showWordWith0x, hexText)+import EVM.Types (Addr, W256, hexText) import EVM.Concrete (Word, w256)-import EVM (EVM, Contract, initialContract, nonce, balance, external)+import EVM (EVM, Contract, StorageModel, initialContract, nonce, balance, external) -import qualified EVM as EVM+import qualified EVM import Control.Lens hiding ((.=))+import Control.Monad.Reader import Control.Monad.Trans.Maybe+import Data.SBV.Trans.Control+import qualified Data.SBV.Internals as SBV+import Data.SBV.Trans hiding (Word) import Data.Aeson import Data.Aeson.Lens import Data.ByteString (ByteString)@@ -28,14 +32,12 @@ QueryBalance :: Addr -> RpcQuery W256 QueryNonce :: Addr -> RpcQuery W256 QuerySlot :: Addr -> W256 -> RpcQuery W256+ QueryChainId :: RpcQuery W256 data BlockNumber = Latest | BlockNumber W256 deriving instance Show (RpcQuery a) -mkr :: Addr-mkr = 0xc66ea802717bfb9833400264dd12c2bceaa34a6d- rpc :: String -> [String] -> Value rpc method args = object [ "jsonrpc" .= ("2.0" :: String)@@ -48,14 +50,14 @@ toRPC :: a -> String instance ToRPC Addr where- toRPC = showAddrWith0x+ toRPC = show instance ToRPC W256 where- toRPC = showWordWith0x+ toRPC = show instance ToRPC BlockNumber where toRPC Latest = "latest"- toRPC (BlockNumber n) = showWordWith0x n+ toRPC (BlockNumber n) = show n readText :: Read a => Text -> a readText = read . unpack@@ -68,7 +70,7 @@ -> IO (Maybe a) fetchQuery n f q = do x <- case q of- QueryCode addr -> do+ QueryCode addr -> fmap hexText <$> f (rpc "eth_getCode" [toRPC addr, toRPC n]) QueryNonce addr ->@@ -80,6 +82,9 @@ QuerySlot addr slot -> fmap readText <$> f (rpc "eth_getStorageAt" [toRPC addr, toRPC slot, toRPC n])+ QueryChainId ->+ fmap readText <$>+ f (rpc "eth_chainId" [toRPC n]) return x fetchWithSession :: Text -> Session -> Value -> IO (Maybe Text)@@ -88,8 +93,8 @@ return (r ^? responseBody . key "result" . _String) fetchContractWithSession- :: BlockNumber -> Text -> Session -> Addr -> IO (Maybe Contract)-fetchContractWithSession n url sess addr = runMaybeT $ do+ :: BlockNumber -> Text -> Addr -> Session -> IO (Maybe Contract)+fetchContractWithSession n url addr sess = runMaybeT $ do let fetch :: Show a => RpcQuery a -> IO (Maybe a) fetch = fetchQuery n (fetchWithSession url sess)@@ -99,46 +104,113 @@ theBalance <- MaybeT $ fetch (QueryBalance addr) return $- initialContract theCode+ initialContract (EVM.RuntimeCode theCode) & set nonce (w256 theNonce) & set balance (w256 theBalance) & set external True fetchSlotWithSession :: BlockNumber -> Text -> Session -> Addr -> W256 -> IO (Maybe Word)-fetchSlotWithSession n url sess addr slot = do+fetchSlotWithSession n url sess addr slot = fmap w256 <$> fetchQuery n (fetchWithSession url sess) (QuerySlot addr slot) fetchContractFrom :: BlockNumber -> Text -> Addr -> IO (Maybe Contract) fetchContractFrom n url addr = Session.withAPISession- (flip (fetchContractWithSession n url) addr)+ (fetchContractWithSession n url addr) fetchSlotFrom :: BlockNumber -> Text -> Addr -> W256 -> IO (Maybe Word) fetchSlotFrom n url addr slot = Session.withAPISession (\s -> fetchSlotWithSession n url s addr slot) -http :: BlockNumber -> Text -> EVM.Query -> IO (EVM ())-http n url q = do+http :: BlockNumber -> Text -> Fetcher+http n url q = case q of EVM.PleaseFetchContract addr continue -> fetchContractFrom n url addr >>= \case- Just x -> do+ Just x -> return (continue x) Nothing -> error ("oracle error: " ++ show q) EVM.PleaseFetchSlot addr slot continue -> fetchSlotFrom n url addr (fromIntegral slot) >>= \case Just x -> return (continue x) Nothing -> error ("oracle error: " ++ show q)+ EVM.PleaseAskSMT _ _ _ -> error "smt calls not available for this oracle" -zero :: Monad m => EVM.Query -> m (EVM ())-zero q = do+zero :: Fetcher+zero q = case q of EVM.PleaseFetchContract _ continue ->- return (continue (initialContract ""))+ return (continue (initialContract (EVM.RuntimeCode mempty))) EVM.PleaseFetchSlot _ _ continue -> return (continue 0)+ EVM.PleaseAskSMT _ _ continue ->+ return $ continue EVM.Unknown ++ type Fetcher = EVM.Query -> IO (EVM ())++-- smtsolving + (http or zero)+oracle :: SBV.State -> Maybe (BlockNumber, Text) -> StorageModel -> Fetcher+oracle state info model q = do+ case q of+ EVM.PleaseAskSMT jumpcondition pathconditions continue ->+ flip runReaderT state $ SBV.runQueryT $ do+ let pathconds = sAnd pathconditions+ noJump <- checksat $ pathconds .&& jumpcondition ./= 0+ case noJump of+ -- Unsat means condition+ -- cannot be nonzero+ Unsat -> do jump <- checksat $ pathconds .&& jumpcondition .== 0+ -- can it be zero?+ case jump of+ -- No. We are on an inconsistent path.+ Unsat -> return $ continue EVM.Inconsistent+ -- Yes. It must be 0.+ Sat -> return $ continue (EVM.Iszero True)+ -- Assume 0 is still possible.+ Unk -> return $ continue (EVM.Iszero True)+ -- Sat means its possible for condition+ -- to be nonzero.+ Sat -> do jump <- checksat $ pathconds .&& jumpcondition .== 0+ -- can it also be zero?+ case jump of+ -- No. It must be nonzero+ Unsat -> return $ continue (EVM.Iszero False)+ -- Yes. Both branches possible+ Sat -> return $ continue EVM.Unknown+ -- Explore both branches in case of timeout+ Unk -> return $ continue EVM.Unknown++ -- If the query times out, we simply explore both paths+ Unk -> return $ continue EVM.Unknown+ -- if we are using a symbolic storage model,+ -- we generate a new array to the fetched contract here+ EVM.PleaseFetchContract addr continue -> do+ contract <- case info of+ Nothing -> return $ Just (initialContract (EVM.RuntimeCode mempty))+ Just (n, url) -> fetchContractFrom n url addr+ case contract of+ Just x -> case model of+ EVM.ConcreteS -> return $ continue x+ EVM.InitialS -> return $ continue $ x & set EVM.storage (EVM.Symbolic $ SBV.sListArray 0 [])+ EVM.SymbolicS -> + flip runReaderT state $ SBV.runQueryT $ do+ store <- freshArray_ Nothing+ return $ continue $ x & set EVM.storage (EVM.Symbolic store)+ Nothing -> error ("oracle error: " ++ show q)++ --- for other queries (there's only slot left right now) we default to zero or http+ _ -> case info of+ Nothing -> zero q+ Just (n, url) -> http n url q++checksat :: SBool -> Query CheckSatResult+checksat b = do resetAssertions+ constrain b+ m <- checkSat+ resetAssertions+ return m
@@ -12,6 +12,7 @@ import EVM.Dapp (DappInfo, dappSources) import EVM.Solidity (sourceAsts)+import EVM.Demand (demand) -- We query and alter the Solidity code using the compiler's AST. -- The AST is a deep JSON structure, so we use Aeson and Lens.@@ -26,14 +27,19 @@ import qualified Data.Graph.Inductive.Query.BFS as Fgl import qualified Data.Graph.Inductive.Query.DFS as Fgl +-- The Solidity version pragmas can be arbitrary SemVer ranges,+-- so we use this library to parse them.+import Data.SemVer (SemVerRange, parseSemVerRange)+import qualified Data.SemVer as SemVer+ import Control.Monad (forM) import Data.ByteString (ByteString) import Data.Foldable (foldl', toList)-import Data.List (sort)+import Data.List (sort, nub) import Data.Map (Map, (!))-import Data.Maybe (mapMaybe, isJust, fromMaybe)+import Data.Maybe (mapMaybe, isJust) import Data.Monoid ((<>))-import Data.Text (Text, unpack, pack, intercalate)+import Data.Text (Text, unpack, pack) import Data.Text.Encoding (encodeUtf8) import Text.Read (readMaybe) @@ -122,6 +128,10 @@ , stripImportsAndPragmas src (asts ! path), "\n" ] + -- Force all evaluation before any printing happens, to avoid+ -- partial output.+ demand target; demand pragma; demand sources+ -- Finally print the whole concatenation. putStrLn $ "// hevm: flattened sources of " <> unpack target putStrLn (unpack pragma)@@ -134,14 +144,20 @@ case mapMaybe versions asts of [] -> error "no Solidity version pragmas in any source files" xs ->- "pragma solidity ^"- <> intercalate "." (map (pack . show) (maximum xs))+ "pragma solidity "+ <> pack (show (rangeIntersection xs)) <> ";\n" where- -- Get the version components from a source file's pragma,+ -- Simple way to combine many SemVer ranges. We don't actually+ -- optimize these boolean expressions, so the resulting pragma+ -- might be redundant, like ">=0.4.23 >=0.5.0 <0.6.0".+ rangeIntersection :: [SemVerRange] -> SemVerRange+ rangeIntersection = foldr1 SemVer.And . nub . sort++ -- Get the semantic version range from a source file's pragma, -- or nothing if no pragma present.- versions :: Value -> Maybe [Int]+ versions :: Value -> Maybe SemVerRange versions ast = fmap grok components where pragma :: Maybe Value@@ -155,14 +171,16 @@ components = fmap toList (pragma >>= preview (key "attributes" . key "literals" . _Array)) - grok :: [Value] -> [Int]+ grok :: [Value] -> SemVerRange grok = \case- [String "solidity", String _prefix, String a, String b] ->- map- (fromMaybe- (error . Text.unpack $ "bad Solidity version: " <> a <> b)- . readAs)- (Text.splitOn "." (a <> b))+ String "solidity" : xs ->+ let+ rangeText = mconcat [x | String x <- xs]+ in+ case parseSemVerRange rangeText of+ Right r -> r+ Left _ ->+ error ("failed to parse SemVer range " ++ show rangeText) x -> error ("unrecognized pragma: " ++ show x)
@@ -1,3 +1,5 @@+{-# Language DataKinds #-}+{-# LANGUAGE OverloadedStrings #-} module EVM.Format where import Prelude hiding (Word)@@ -5,14 +7,14 @@ import EVM (VM, cheatCode, traceForest, traceData, Error (..)) import EVM (Trace, TraceData (..), Log (..), Query (..), FrameContext (..)) import EVM.Dapp (DappInfo, dappSolcByHash, showTraceLocation, dappEventMap)-import EVM.Concrete (Word (..))+import EVM.Concrete (Word (..), wordValue)+import EVM.Symbolic (maybeLitWord, Buffer(..), len) import EVM.Types (W256 (..), num) import EVM.ABI (AbiValue (..), Event (..), AbiType (..))-import EVM.ABI (Indexed (Indexed, NotIndexed), getAbiSeq, getAbi)-import EVM.ABI (abiTypeSolidity, parseTypeName)+import EVM.ABI (Indexed (NotIndexed), getAbiSeq, getAbi)+import EVM.ABI (parseTypeName) import EVM.Solidity (SolcContract, contractName, abiMap) import EVM.Solidity (methodOutput, methodSignature)-import EVM.Concrete (wordValue) import Control.Arrow ((>>>)) import Control.Lens (view, preview, ix, _2, to, _Just)@@ -22,24 +24,23 @@ import Data.ByteString.Lazy (toStrict, fromStrict) import Data.DoubleWord (signedWord) import Data.Foldable (toList)-import Data.Map (Map)-import Data.Maybe (catMaybes)+import Data.Maybe (catMaybes, fromMaybe) import Data.Monoid ((<>)) import Data.Text (Text, pack, unpack, intercalate) import Data.Text (dropEnd, splitOn) import Data.Text.Encoding (decodeUtf8, decodeUtf8') import Data.Tree.View (showTree)-import Data.Vector (Vector)+import Data.Vector (Vector, fromList) import Numeric (showHex) import qualified Data.ByteString as BS import qualified Data.Char as Char import qualified Data.Map as Map-import qualified Data.Scientific as Scientific import qualified Data.Text as Text data Signedness = Signed | Unsigned+ deriving (Show) showDec :: Signedness -> W256 -> Text showDec signed (W256 w) =@@ -47,36 +48,25 @@ i = case signed of Signed -> num (signedWord w) Unsigned -> num w- in if i == num cheatCode then "<hevm cheat address>"- else- if abs i > 1000000000000- then- "~" <> pack (Scientific.formatScientific- Scientific.Generic- (Just 8)- (fromIntegral i))- else- showDecExact i--showDecExact :: Integer -> Text-showDecExact = humanizeInteger+ else if (i :: Integer) == 2 ^ (256 :: Integer) - 1+ then "MAX_UINT256"+ else Text.pack (show (i :: Integer)) showWordExact :: Word -> Text showWordExact (C _ (W256 w)) = humanizeInteger w humanizeInteger :: (Num a, Integral a, Show a) => a -> Text humanizeInteger =- ( Text.intercalate ","+ Text.intercalate "," . reverse . map Text.reverse . Text.chunksOf 3 . Text.reverse . Text.pack . show- ) -- TODO: make polymorphic showAbiValues :: Vector AbiValue -> Text@@ -106,6 +96,8 @@ showAbiArray xs showAbiValue (AbiArrayDynamic _ xs) = showAbiArray xs+showAbiValue (AbiTuple v) =+ showAbiValues v isPrintable :: ByteString -> Bool isPrintable =@@ -121,16 +113,28 @@ then formatQString s else formatBinary b +formatSBytes :: Buffer -> Text+formatSBytes (SymbolicBuffer b) = "<" <> pack (show (length b)) <> " symbolic bytes>"+formatSBytes (ConcreteBuffer b) = formatBytes b+ formatQString :: ByteString -> Text formatQString = pack . show formatString :: ByteString -> Text formatString bs = decodeUtf8 (fst (BS.spanEnd (== 0) bs)) +formatSString :: Buffer -> Text+formatSString (SymbolicBuffer bs) = "<" <> pack (show (length bs)) <> " symbolic bytes (string)>"+formatSString (ConcreteBuffer bs) = formatString bs+ formatBinary :: ByteString -> Text formatBinary = (<>) "0x" . decodeUtf8 . toStrict . toLazyByteString . byteStringHex +formatSBinary :: Buffer -> Text+formatSBinary (SymbolicBuffer bs) = "<" <> pack (show (length bs)) <> " symbolic bytes>"+formatSBinary (ConcreteBuffer bs) = formatBinary bs+ showTraceTree :: DappInfo -> VM -> Text showTraceTree dapp = traceForest@@ -148,58 +152,68 @@ in case view traceData trace of EventTrace (Log _ bytes topics) -> case topics of- (t:_) ->- let event = getEvent t (view dappEventMap dapp)- -- indexed types are in the remaining topics- types = getEventUnindexedTypes event- in case event of- -- todo: catch ds-note in Anonymous case- Just (Event name _ _) ->- mconcat- [ "\x1b[36m"- , name- , showEvent types bytes- , "\x1b[0m"- ] <> pos- Nothing ->- mconcat- [ "\x1b[36m"- , "<unknown-event> "- , formatBinary bytes- , mconcat (map (pack . show) topics)- , "\x1b[0m"- ] <> pos- _ ->- "log" <> pos+ [] ->+ mconcat+ [ "\x1b[36m"+ , "log0("+ , formatSBinary bytes+ , ")"+ , "\x1b[0m"+ ] <> pos+ (topic:_) ->+ let unknownTopic = -- todo: catch ds-note+ mconcat+ [ "\x1b[36m"+ , "log" <> (pack (show (length topics))) <> "("+ , formatSBinary bytes <> ", "+ , intercalate ", " (map (pack . show) topics) <> ")"+ , "\x1b[0m"+ ] <> pos++ in case maybeLitWord topic of+ Just top -> case Map.lookup (wordValue top) (view dappEventMap dapp) of+ Just (Event name _ types) ->+ mconcat+ [ "\x1b[36m"+ , name+ , showValues [t | (t, NotIndexed) <- types] bytes+ -- todo: show indexed+ , "\x1b[0m"+ ] <> pos+ Nothing -> unknownTopic+ Nothing -> unknownTopic+ QueryTrace q -> case q of PleaseFetchContract addr _ -> "fetch contract " <> pack (show addr) <> pos PleaseFetchSlot addr slot _ -> "fetch storage slot " <> pack (show slot) <> " from " <> pack (show addr) <> pos+ PleaseAskSMT _ _ _ ->+ "ask smt" <> pos ErrorTrace e -> case e of- Revert output ->- "\x1b[91merror\x1b[0m " <> "Revert " <> formatBinary output <> pos+ Revert out ->+ "\x1b[91merror\x1b[0m " <> "Revert " <> showError out <> pos _ -> "\x1b[91merror\x1b[0m " <> pack (show e) <> pos - ReturnTrace output (CallContext _ _ hash (Just abi) _ _) ->+ ReturnTrace out (CallContext _ _ hash (Just abi) _ _ _) -> case getAbiMethodOutput dapp hash abi of Nothing ->- "← " <> formatBinary output+ "← " <> formatSBinary out Just (_, t) ->- "← " <> abiTypeSolidity t <> " " <> showValue t output- ReturnTrace output (CallContext {}) ->- "← " <> formatBinary output- ReturnTrace output (CreationContext {}) ->- "← " <> pack (show (BS.length output)) <> " bytes of code"+ "← " <> pack (show t) <> " " <> showValue t out+ ReturnTrace out (CallContext {}) ->+ "← " <> formatSBinary out+ ReturnTrace out (CreationContext {}) ->+ "← " <> pack (show (len out)) <> " bytes of code" EntryTrace t -> t- FrameTrace (CreationContext hash) ->+ FrameTrace (CreationContext hash _ _ ) -> "create " <> maybeContractName (preview (dappSolcByHash . ix hash . _2) dapp) <> pos- FrameTrace (CallContext _ _ hash abi calldata _) ->+ FrameTrace (CallContext _ _ hash abi calldata _ _) -> case preview (dappSolcByHash . ix hash . _2) dapp of Nothing -> "call [unknown]" <> pos@@ -208,10 +222,10 @@ <> "\x1b[1m" <> view (contractName . to contractNamePart) solc <> "::"- <> maybe ("[fallback function]")- (\x -> maybe "[unknown method]" id (maybeAbiName solc x))+ <> maybe "[fallback function]"+ (fromMaybe "[unknown method]" . maybeAbiName solc) abi- <> maybe ("(" <> formatBinary calldata <> ")")+ <> maybe ("(" <> formatSBinary calldata <> ")") -- todo: if unknown method, then just show raw calldata (\x -> showCall (catMaybes x) calldata) (abi >>= fmap getAbiTypes . maybeAbiName solc)@@ -229,28 +243,32 @@ dapp getAbiTypes :: Text -> [Maybe AbiType]-getAbiTypes abi = map parseTypeName types+getAbiTypes abi = map (parseTypeName mempty) types where types = filter (/= "") $ splitOn "," (dropEnd 1 (last (splitOn "(" abi))) -showCall :: [AbiType] -> ByteString -> Text-showCall ts bs =- case runGetOrFail (getAbiSeq (length ts) ts)- (fromStrict (BS.drop 4 bs)) of+showCall :: [AbiType] -> Buffer -> Text+showCall ts (SymbolicBuffer bs) = showValues ts $ SymbolicBuffer (drop 4 bs)+showCall ts (ConcreteBuffer bs) = showValues ts $ ConcreteBuffer (BS.drop 4 bs)++showError :: ByteString -> Text+showError bs = case BS.take 4 bs of+ -- Method ID for Error(string)+ "\b\195y\160" -> showCall [AbiStringType] (ConcreteBuffer bs)+ _ -> formatBinary bs++showValues :: [AbiType] -> Buffer -> Text+showValues ts (SymbolicBuffer sbs) = "symbolic: " <> (pack . show $ AbiTupleType (fromList ts))+showValues ts (ConcreteBuffer bs) =+ case runGetOrFail (getAbiSeq (length ts) ts) (fromStrict bs) of Right (_, _, xs) -> showAbiValues xs Left (_, _, _) -> formatBinary bs -showEvent :: [AbiType] -> ByteString -> Text-showEvent ts bs =- case runGetOrFail (getAbiSeq (length ts) ts)- (fromStrict bs) of- Right (_, _, abivals) -> showAbiValues abivals- Left (_,_,_) -> error "lol"--showValue :: AbiType -> ByteString -> Text-showValue t bs =+showValue :: AbiType -> Buffer -> Text+showValue t (SymbolicBuffer _) = "symbolic: " <> (pack $ show t)+showValue t (ConcreteBuffer bs) = case runGetOrFail (getAbi t) (fromStrict bs) of Right (_, _, x) -> showAbiValue x Left (_, _, _) -> formatBinary bs@@ -267,18 +285,3 @@ contractPathPart :: Text -> Text contractPathPart x = Text.split (== ':') x !! 0--getEvent :: Word -> Map W256 Event -> Maybe Event-getEvent w events = Map.lookup (wordValue w) events--getEventName :: Maybe Event -> Text-getEventName (Just (Event name _ _)) = name-getEventName Nothing = "<unknown-event>"--getEventUnindexedTypes :: Maybe Event -> [AbiType]-getEventUnindexedTypes Nothing = []-getEventUnindexedTypes (Just (Event _ _ xs)) = [x | (x, NotIndexed) <- xs]--getEventIndexedTypes :: Maybe Event -> [AbiType]-getEventIndexedTypes Nothing = []-getEventIndexedTypes (Just (Event _ _ xs)) = [x | (x, Indexed) <- xs]
@@ -1,10 +1,4 @@-{-# Language CPP #-}--#ifdef __GHCJS__-{-# Language JavaScriptFFI #-}-#endif--module EVM.Keccak (keccak, abiKeccak, newContractAddress) where+module EVM.Keccak (keccak, abiKeccak) where import EVM.Types @@ -12,55 +6,24 @@ import Data.Bits import Data.ByteString (ByteString)+ import qualified Data.ByteString as BS import Data.Word -#ifdef __GHCJS__-import qualified Data.JSString as JS-import qualified Data.Text.Encoding as Text-import qualified Data.Text as Text-import qualified Data.ByteString.Base64 as BS64--foreign import javascript unsafe- "keccakBase64($1)"- keccakBase64 :: JS.JSString -> JS.JSString--keccakBytes =- BS64.encode- >>> Text.decodeUtf8- >>> Text.unpack- >>> JS.pack- >>> keccakBase64- >>> JS.unpack- >>> Text.pack- >>> Text.encodeUtf8- >>> BS64.decodeLenient--#else import Crypto.Hash import qualified Data.ByteArray as BA +keccakBytes :: ByteString -> ByteString keccakBytes = (hash :: ByteString -> Digest Keccak_256) >>> BA.unpack >>> BS.pack -#endif -keccakBytes :: ByteString -> ByteString- word32 :: [Word8] -> Word32 word32 xs = sum [ fromIntegral x `shiftL` (8*n) | (n, x) <- zip [0..] (reverse xs) ] -octets :: W256 -> [Word8]-octets x =- dropWhile (== 0) [fromIntegral (shiftR x (8 * i)) | i <- reverse [0..31]]--octets160 :: Addr -> [Word8]-octets160 x =- dropWhile (== 0) [fromIntegral (shiftR x (8 * i)) | i <- reverse [0..19]]- keccak :: ByteString -> W256 keccak = keccakBytes@@ -73,30 +36,3 @@ >>> BS.take 4 >>> BS.unpack >>> word32--rlpWord256 :: W256 -> ByteString-rlpWord256 0 = BS.pack [0x80]-rlpWord256 x | x <= 0x7f = BS.pack [fromIntegral x]-rlpWord256 x =- let xs = octets x- in BS.pack ([0x80 + fromIntegral (length xs)] ++ xs)--rlpWord160 :: Addr -> ByteString-rlpWord160 0 = BS.pack [0x80]-rlpWord160 x =- let xs = octets160 x- in BS.pack ([0x80 + fromIntegral (length xs)] ++ xs)--rlpList :: [ByteString] -> ByteString-rlpList xs =- let n = sum (map BS.length xs)- in if n <= 55- then BS.cons (fromIntegral (0xc0 + n)) (BS.concat xs)- else- let ns = rlpWord256 (fromIntegral n)- in BS.cons (fromIntegral (0xf7 + BS.length ns)) (BS.concat (ns : xs))--newContractAddress :: Addr -> W256 -> Addr-newContractAddress a n =- fromIntegral- (keccak $ rlpList [rlpWord160 a, rlpWord256 n])
@@ -1,7 +1,11 @@-module EVM.Op (Op (..)) where+module EVM.Op+ ( Op (..)+ , opString+ ) where import EVM.Types (W256) import Data.Word (Word8)+import Numeric (showHex) data Op = OpStop@@ -27,6 +31,9 @@ | OpXor | OpNot | OpByte+ | OpShl+ | OpShr+ | OpSar | OpSha3 | OpAddress | OpBalance@@ -43,12 +50,15 @@ | OpExtcodecopy | OpReturndatasize | OpReturndatacopy+ | OpExtcodehash | OpBlockhash | OpCoinbase | OpTimestamp | OpNumber | OpDifficulty | OpGaslimit+ | OpChainid+ | OpSelfbalance | OpPop | OpMload | OpMstore@@ -67,6 +77,7 @@ | OpCallcode | OpReturn | OpDelegatecall+ | OpCreate2 | OpRevert | OpSelfdestruct | OpDup !Word8@@ -75,3 +86,85 @@ | OpPush !W256 | OpUnknown Word8 deriving (Show, Eq)++opString :: (Integral a, Show a) => (a, Op) -> String+opString (i, o) = let showPc x | x < 0x10 = '0' : showHex x ""+ | otherwise = showHex x ""+ in showPc i <> " " ++ case o of+ OpStop -> "STOP"+ OpAdd -> "ADD"+ OpMul -> "MUL"+ OpSub -> "SUB"+ OpDiv -> "DIV"+ OpSdiv -> "SDIV"+ OpMod -> "MOD"+ OpSmod -> "SMOD"+ OpAddmod -> "ADDMOD"+ OpMulmod -> "MULMOD"+ OpExp -> "EXP"+ OpSignextend -> "SIGNEXTEND"+ OpLt -> "LT"+ OpGt -> "GT"+ OpSlt -> "SLT"+ OpSgt -> "SGT"+ OpEq -> "EQ"+ OpIszero -> "ISZERO"+ OpAnd -> "AND"+ OpOr -> "OR"+ OpXor -> "XOR"+ OpNot -> "NOT"+ OpByte -> "BYTE"+ OpShl -> "SHL"+ OpShr -> "SHR"+ OpSar -> "SAR"+ OpSha3 -> "SHA3"+ OpAddress -> "ADDRESS"+ OpBalance -> "BALANCE"+ OpOrigin -> "ORIGIN"+ OpCaller -> "CALLER"+ OpCallvalue -> "CALLVALUE"+ OpCalldataload -> "CALLDATALOAD"+ OpCalldatasize -> "CALLDATASIZE"+ OpCalldatacopy -> "CALLDATACOPY"+ OpCodesize -> "CODESIZE"+ OpCodecopy -> "CODECOPY"+ OpGasprice -> "GASPRICE"+ OpExtcodesize -> "EXTCODESIZE"+ OpExtcodecopy -> "EXTCODECOPY"+ OpReturndatasize -> "RETURNDATASIZE"+ OpReturndatacopy -> "RETURNDATACOPY"+ OpExtcodehash -> "EXTCODEHASH"+ OpBlockhash -> "BLOCKHASH"+ OpCoinbase -> "COINBASE"+ OpTimestamp -> "TIMESTAMP"+ OpNumber -> "NUMBER"+ OpDifficulty -> "DIFFICULTY"+ OpGaslimit -> "GASLIMIT"+ OpChainid -> "CHAINID"+ OpSelfbalance -> "SELFBALANCE"+ OpPop -> "POP"+ OpMload -> "MLOAD"+ OpMstore -> "MSTORE"+ OpMstore8 -> "MSTORE8"+ OpSload -> "SLOAD"+ OpSstore -> "SSTORE"+ OpJump -> "JUMP"+ OpJumpi -> "JUMPI"+ OpPc -> "PC"+ OpMsize -> "MSIZE"+ OpGas -> "GAS"+ OpJumpdest -> "JUMPDEST"+ OpCreate -> "CREATE"+ OpCall -> "CALL"+ OpStaticcall -> "STATICCALL"+ OpCallcode -> "CALLCODE"+ OpReturn -> "RETURN"+ OpDelegatecall -> "DELEGATECALL"+ OpCreate2 -> "CREATE2"+ OpSelfdestruct -> "SELFDESTRUCT"+ OpDup x -> "DUP" ++ show x+ OpSwap x -> "SWAP" ++ show x+ OpLog x -> "LOG" ++ show x+ OpPush x -> "PUSH " ++ show x+ OpRevert -> "REVERT"+ OpUnknown x -> "UNKNOWN " ++ show x
@@ -0,0 +1,232 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleInstances #-}++module EVM.Patricia where++import EVM.Keccak+import EVM.RLP+import EVM.Types++import Control.Monad.Free+import Control.Monad.State+import Data.ByteString (ByteString)+import Data.Foldable (toList)+import Data.List (stripPrefix)+import Data.Monoid ((<>))+import Data.Sequence (Seq)++import qualified Data.ByteString as BS+import qualified Data.Map as Map+import qualified Data.Sequence as Seq++data KV k v a+ = Put k v a+ | Get k (v -> a)+ deriving (Functor)++newtype DB k v a = DB (Free (KV k v) a)+ deriving (Functor, Applicative, Monad)++insertDB :: k -> v -> DB k v ()+insertDB k v = DB $ liftF $ Put k v ()++lookupDB :: k -> DB k v v+lookupDB k = DB $ liftF $ Get k id++-- Collapses a series of puts and gets down to the monad of your choice+runDB :: Monad m+ => (k -> v -> m ()) -- ^ The 'put' function for our desired monad+ -> (k -> m v) -- ^ The 'get' function for the same monad+ -> DB k v a -- ^ The puts and gets to execute+ -> m a+runDB putt gett (DB ops) = go ops+ where+ go (Pure a) = return a+ go (Free (Put k v next)) = putt k v >> go next+ go (Free (Get k handler)) = gett k >>= go . handler++type Path = [Nibble]++data Ref = Hash ByteString | Literal Node+ deriving (Eq)++instance Show Ref where+ show (Hash d) = show (ByteStringS d)+ show (Literal n) = show n++data Node = Empty+ | Shortcut Path (Either Ref ByteString)+ | Full (Seq Ref) ByteString+ deriving (Show, Eq)++-- the function HP from Appendix C of yellow paper+encodePath :: Path -> Bool -> ByteString+encodePath p isTerminal | even (length p)+ = packNibbles $ Nibble flag : Nibble 0 : p+ | otherwise+ = packNibbles $ Nibble (flag + 1) : p+ where flag = if isTerminal then 2 else 0++rlpRef :: Ref -> RLP+rlpRef (Hash d) = BS d+rlpRef (Literal n) = rlpNode n++rlpNode :: Node -> RLP+rlpNode Empty = BS mempty+rlpNode (Shortcut path (Right val)) = List [BS $ encodePath path True, BS val]+rlpNode (Shortcut path (Left ref)) = List [BS $ encodePath path False, rlpRef ref]+rlpNode (Full refs val) = List $ toList (fmap rlpRef refs) <> [BS val]++type NodeDB = DB ByteString Node++instance Show (NodeDB Node) where+ show = show++putNode :: Node -> NodeDB Ref+putNode node =+ let bytes = rlpencode $ rlpNode node+ digest = word256Bytes $ keccak bytes+ in if BS.length bytes < 32+ then return $ Literal node+ else do+ insertDB digest node+ return $ Hash digest++getNode :: Ref -> NodeDB Node+getNode (Hash d) = lookupDB d+getNode (Literal n) = return n++lookupPath :: Ref -> Path -> NodeDB ByteString+lookupPath root path = getNode root >>= getVal path++getVal :: Path -> Node -> NodeDB ByteString+getVal _ Empty = return BS.empty+getVal path (Shortcut nodePath ref) =+ case (stripPrefix nodePath path, ref) of+ (Just [], Right value) -> return value+ (Just remaining, Left key) -> lookupPath key remaining+ _ -> return BS.empty++getVal [] (Full _ val) = return val+getVal (p:ps) (Full refs _) = lookupPath (refs `Seq.index` (num p)) ps++emptyRef :: Ref+emptyRef = Literal Empty++emptyRefs :: Seq Ref+emptyRefs = Seq.replicate 16 emptyRef++addPrefix :: Path -> Node -> NodeDB Node+addPrefix _ Empty = return Empty+addPrefix [] node = return node+addPrefix path (Shortcut p v) = return $ Shortcut (path <> p) v+addPrefix path n = Shortcut path . Left <$> putNode n++insertRef :: Ref -> Path -> ByteString -> NodeDB Ref+insertRef ref p val = do root <- getNode ref+ newNode <- if val == BS.empty+ then delete root p+ else update root p val+ putNode newNode++update :: Node -> Path -> ByteString -> NodeDB Node+update Empty p new = return $ Shortcut p (Right new)+update (Full refs _) [] new = return (Full refs new)+update (Full refs old) (p:ps) new = do+ newRef <- insertRef (refs `Seq.index` (num p)) ps new+ return $ Full (Seq.update (num p) newRef refs) old+update (Shortcut (o:os) (Right old)) [] new = do+ newRef <- insertRef emptyRef os old+ return $ Full (Seq.update (num o) newRef emptyRefs) new+update (Shortcut [] (Right old)) (p:ps) new = do+ newRef <- insertRef emptyRef ps new+ return $ Full (Seq.update (num p) newRef emptyRefs) old+update (Shortcut [] (Right _)) [] new =+ return $ Shortcut [] (Right new)+update (Shortcut (o:os) to) (p:ps) new | o == p+ = update (Shortcut os to) ps new >>= addPrefix [o]+ | otherwise = do+ oldRef <- case to of+ (Left ref) -> getNode ref >>= addPrefix os >>= putNode+ (Right val) -> insertRef emptyRef os val+ newRef <- insertRef emptyRef ps new+ let refs = Seq.update (num p) newRef $ Seq.update (num o) oldRef emptyRefs+ return $ Full refs BS.empty+update (Shortcut (o:os) (Left ref)) [] new = do+ newRef <- getNode ref >>= addPrefix os >>= putNode+ return $ Full (Seq.update (num o) newRef emptyRefs) new+update (Shortcut cut (Left ref)) ps new = do+ newRef <- insertRef ref ps new+ return $ Shortcut cut (Left newRef)++delete :: Node -> Path -> NodeDB Node+delete Empty _ = return Empty+delete (Shortcut [] (Right _)) [] = return Empty+delete n@(Shortcut [] (Right _)) _ = return n+delete (Shortcut [] (Left ref)) p = do node <- getNode ref+ delete node p+delete n@(Shortcut _ _) [] = return n+delete n@(Shortcut (o:os) to) (p:ps) | p == o+ = delete (Shortcut os to) ps >>= addPrefix [o]+ | otherwise+ = return n+delete (Full refs _) [] | refs == emptyRefs+ = return Empty+ | otherwise+ = return (Full refs BS.empty)+delete (Full refs val) (p:ps) = do+ newRef <- insertRef (refs `Seq.index` (num p)) ps BS.empty+ let newRefs = Seq.update (num p) newRef refs+ nonEmpties = filter (\(_, ref) -> ref /= emptyRef) $ zip [0..15] $ toList newRefs+ case (nonEmpties, BS.null val) of+ ([], True) -> return Empty+ ([(n, ref)], True) -> getNode ref >>= addPrefix [Nibble n]+ _ -> return $ Full newRefs val++insert :: Ref -> ByteString -> ByteString -> NodeDB Ref+insert ref key = insertRef ref (unpackNibbles key)++lookupIn :: Ref -> ByteString -> NodeDB ByteString+lookupIn ref bs = lookupPath ref $ unpackNibbles bs++type Trie = StateT Ref NodeDB++runTrie :: DB ByteString ByteString a -> Trie a+runTrie = runDB putDB getDB+ where+ putDB key val = do+ ref <- get+ newRef <- lift $ insert ref key val+ put newRef+ getDB key = do+ ref <- get+ lift $ lookupIn ref key++type MapDB k v a = StateT (Map.Map k v) Maybe a++runMapDB :: Ord k => DB k v a -> MapDB k v a+runMapDB = runDB putDB getDB+ where+ getDB key = do+ mmap <- get+ lift $ Map.lookup key mmap+ putDB key value = do+ mmap <- get+ let newMap = Map.insert key value mmap+ put newMap+++insertValues :: [(ByteString, ByteString)] -> Maybe Ref+insertValues inputs =+ let trie = runTrie $ mapM_ insertPair inputs+ mapDB = runMapDB $ runStateT trie (Literal Empty)+ result = snd <$> evalStateT mapDB Map.empty+ insertPair (key, value) = insertDB key value+ in result++calcRoot :: [(ByteString, ByteString)] -> Maybe ByteString+calcRoot vs = case insertValues vs of+ Just (Hash b) -> Just b+ Just (Literal n) -> Just $ word256Bytes $ keccak $ rlpencode $ rlpNode n+ Nothing -> Nothing
@@ -0,0 +1,79 @@+module EVM.RLP where++import Prelude hiding (drop, head)+import EVM.Types+import Data.Bits (shiftR)+import Data.ByteString (ByteString, drop, head)+import qualified Data.ByteString as BS++data RLP = BS ByteString | List [RLP] deriving Eq++instance Show RLP where+ show (BS str) = show (ByteStringS str)+ show (List list) = show list++slice :: Int -> Int -> ByteString -> ByteString+slice offset size bs = BS.take size $ BS.drop offset bs++-- helper function returning (the length of the prefix, the length of the content, isList boolean, optimal boolean)+itemInfo :: ByteString -> (Int, Int, Bool, Bool)+itemInfo bs | bs == mempty = (0, 0, False, False)+ | otherwise = case head bs of+ x | 0 <= x && x < 128 -> (0, 1, False, True) -- directly encoded byte+ x | 128 <= x && x < 184 -> (1, num x - 128, False, (BS.length bs /= 2) || (127 < (head $ drop 1 bs))) -- short string+ x | 184 <= x && x < 192 -> (1 + pre, len, False, (len > 55) && head (drop 1 bs) /= 0) -- long string+ where pre = num $ x - 183+ len = num $ word $ slice 1 pre bs+ x | 192 <= x && x < 248 -> (1, num $ x - 192, True, True) -- short list+ x -> (1 + pre, len, True, (len > 55) && head (drop 1 bs) /= 0) -- long list+ where pre = num $ x - 247+ len = num $ word $ slice 1 pre bs++rlpdecode :: ByteString -> Maybe RLP+rlpdecode bs | optimal && pre + len == BS.length bs = if isList+ then do+ items <- mapM+ (\(s, e) -> rlpdecode $ slice s e content) $+ rlplengths content 0 len+ Just (List items)+ else Just (BS content)+ | otherwise = Nothing+ where (pre, len, isList, optimal) = itemInfo bs+ content = drop pre bs++rlplengths :: ByteString -> Int -> Int -> [(Int,Int)]+rlplengths bs acc top | acc < top = let (pre, len, _, _) = itemInfo bs+ in (acc, pre + len) : rlplengths (drop (pre + len) bs) (acc + pre + len) top+ | otherwise = []++rlpencode :: RLP -> ByteString+rlpencode (BS bs) = if BS.length bs == 1 && head bs < 128 then bs+ else encodeLen 128 bs+rlpencode (List items) = encodeLen 192 (mconcat $ map rlpencode items)++encodeLen :: Int -> ByteString -> ByteString+encodeLen offset bs | BS.length bs <= 55 = prefix (BS.length bs) <> bs+ | otherwise = prefix lenLen <> lenBytes <> bs+ where+ lenBytes = asBE $ BS.length bs+ prefix n = BS.singleton $ num $ offset + n+ lenLen = BS.length lenBytes + 55++rlpList :: [RLP] -> ByteString+rlpList n = rlpencode $ List n++octets :: W256 -> ByteString+octets x =+ BS.pack $ dropWhile (== 0) [fromIntegral (shiftR x (8 * i)) | i <- reverse [0..31]]++octets160 :: Addr -> ByteString+octets160 x =+ BS.pack $ dropWhile (== 0) [fromIntegral (shiftR x (8 * i)) | i <- reverse [0..19]]++rlpWord256 :: W256 -> RLP+rlpWord256 0 = BS mempty+rlpWord256 n = BS $ octets n++rlpWord160 :: Addr -> RLP+rlpWord160 0 = BS mempty+rlpWord160 n = BS $ octets160 n
@@ -1,15 +1,19 @@ {-# Language DeriveAnyClass #-} {-# Language StrictData #-} {-# Language TemplateHaskell #-}+{-# Language OverloadedStrings #-} module EVM.Solidity ( solidity+ , solcRuntime , JumpType (..) , SolcContract (..)+ , StorageItem (..) , SourceCache (..) , SrcMap (..) , CodeType (..) , Method (..)+ , SlotType (..) , methodName , methodSignature , methodInputs@@ -19,8 +23,10 @@ , contractName , constructorInputs , creationCode+ , functionAbi , makeSrcMaps , readSolc+ , readJSON , runtimeCode , snippetCache , runtimeCodehash@@ -32,6 +38,8 @@ , sourceLines , sourceAsts , stripBytecodeMetadata+ , signature+ , parseMethodInput , lineSubrange , astIdMap , astSrcMap@@ -41,15 +49,23 @@ import EVM.Keccak import EVM.Types +import Codec.CBOR.Term (decodeTerm)+import Codec.CBOR.Read (deserialiseFromBytes) import Control.Applicative import Control.Lens hiding (Indexed) import Data.Aeson (Value (..)) import Data.Aeson.Lens+import Data.Scientific+import Data.Binary.Get (runGet, getWord16be) import Data.ByteString (ByteString)-import Data.Char (isDigit, digitToInt)+import Data.ByteString.Lazy (fromStrict)+import Data.Char (isDigit)+import Data.Either (isRight) import Data.Foldable import Data.Map.Strict (Map) import Data.Maybe+import Data.List.NonEmpty (NonEmpty)+import qualified Data.List.NonEmpty as NonEmpty import Data.Semigroup import Data.Sequence (Seq) import Data.Text (Text, pack, intercalate)@@ -71,6 +87,45 @@ import qualified Data.Text as Text import qualified Data.Vector as Vector +data StorageItem = StorageItem {+ _type :: SlotType,+ _offset :: Int,+ _slot :: Int+ } deriving (Show, Eq)+ +data SlotType+ -- Note that mapping keys can only be elementary;+ -- that excludes arrays, contracts, and mappings.+ = StorageMapping (NonEmpty AbiType) AbiType+ | StorageValue AbiType+-- | StorageArray AbiType+ deriving Eq++instance Show SlotType where+ show (StorageValue t) = show t+ show (StorageMapping (s NonEmpty.:| ss) t) =+ "mapping("+ <> show s+ <> " => "+ <> foldr+ (\x y ->+ "mapping("+ <> show x+ <> " => "+ <> y+ <> ")")+ (show t) ss+ <> ")"++instance Read SlotType where+ readsPrec _ ('m':'a':'p':'p':'i':'n':'g':'(':s) =+ let (lhs:rhs) = Text.splitOn " => " (pack s)+ first = fromJust $ parseTypeName mempty lhs+ target = fromJust $ parseTypeName mempty (Text.replace ")" "" (last rhs))+ rest = fmap (fromJust . (parseTypeName mempty . (Text.replace "mapping(" ""))) (take (length rhs - 1) rhs)+ in [(StorageMapping (first NonEmpty.:| rest) target, "")]+ readsPrec _ s = [(StorageValue $ fromMaybe (error "could not parse storage item") (parseTypeName mempty (pack s)),"")]+ data SolcContract = SolcContract { _runtimeCodehash :: W256 , _creationCodehash :: W256@@ -80,6 +135,7 @@ , _constructorInputs :: [(Text, AbiType)] , _abiMap :: Map Word32 Method , _eventMap :: Map W256 Event+ , _storageLayout :: Maybe (Map Text StorageItem) , _runtimeSrcmap :: Seq SrcMap , _creationSrcmap :: Seq SrcMap , _contractAst :: Value@@ -112,15 +168,16 @@ srcMapOffset :: {-# UNPACK #-} Int, srcMapLength :: {-# UNPACK #-} Int, srcMapFile :: {-# UNPACK #-} Int,- srcMapJump :: JumpType+ srcMapJump :: JumpType,+ srcMapModifierDepth :: {-# UNPACK #-} Int } deriving (Show, Eq, Ord, Generic) data SrcMapParseState- = F1 [Int] Int- | F2 Int [Int] Int- | F3 Int Int [Int] Int- | F4 Int Int Int- | F5 SrcMap+ = F1 String Int+ | F2 Int String Int+ | F3 Int Int String Int+ | F4 Int Int Int (Maybe JumpType)+ | F5 Int Int Int JumpType String | Fe deriving Show @@ -134,45 +191,51 @@ -- Obscure but efficient parser for the Solidity sourcemap format. makeSrcMaps :: Text -> Maybe (Seq SrcMap) makeSrcMaps = (\case (_, Fe, _) -> Nothing; x -> Just (done x))- . Text.foldl' (\x y -> go y x) (mempty, F1 [] 1, SM 0 0 0 JumpRegular)+ . Text.foldl' (flip go) (mempty, F1 [] 1, SM 0 0 0 JumpRegular 0) where- digits ds = digits' (0 :: Int) (0 :: Int) ds- digits' !x _ [] = x- digits' !x !n (d:ds) = digits' (x + d * 10 ^ n) (n + 1) ds- done (xs, s, p) = let (xs', _, _) = go ';' (xs, s, p) in xs'+ readR = read . reverse - go ':' (xs, F1 [] _, p@(SM a _ _ _)) = (xs, F2 a [] 1, p)- go ':' (xs, F1 ds k, p) = (xs, F2 (k * digits ds) [] 1, p)+ go :: Char -> (Seq SrcMap, SrcMapParseState, SrcMap) -> (Seq SrcMap, SrcMapParseState, SrcMap)+ go ':' (xs, F1 [] _, p@(SM a _ _ _ _)) = (xs, F2 a [] 1, p)+ go ':' (xs, F1 ds k, p) = (xs, F2 (k * (readR ds)) [] 1, p) go '-' (xs, F1 [] _, p) = (xs, F1 [] (-1), p)- go d (xs, F1 ds k, p) | isDigit d = (xs, F1 (digitToInt d : ds) k, p)+ go d (xs, F1 ds k, p) | isDigit d = (xs, F1 (d : ds) k, p) go ';' (xs, F1 [] k, p) = (xs |> p, F1 [] k, p)- go ';' (xs, F1 ds k, SM _ b c d) = let p' = SM (k * digits ds) b c d in- (xs |> p', F1 [] 1, p')+ go ';' (xs, F1 ds k, SM _ b c d e) = let p' = SM (k * (readR ds)) b c d e in (xs |> p', F1 [] 1, p') go '-' (xs, F2 a [] _, p) = (xs, F2 a [] (-1), p)- go d (xs, F2 a ds k, p) | isDigit d = (xs, F2 a (digitToInt d : ds) k, p)- go ':' (xs, F2 a [] _, p@(SM _ b _ _)) = (xs, F3 a b [] 1, p)- go ':' (xs, F2 a ds k, p) = (xs, F3 a (k * digits ds) [] 1, p)- go ';' (xs, F2 a [] _, SM _ b c d) = let p' = SM a b c d in (xs |> p', F1 [] 1, p')- go ';' (xs, F2 a ds k, SM _ _ c d) = let p' = SM a (k * digits ds) c d in+ go d (xs, F2 a ds k, p) | isDigit d = (xs, F2 a (d : ds) k, p)+ go ':' (xs, F2 a [] _, p@(SM _ b _ _ _)) = (xs, F3 a b [] 1, p)+ go ':' (xs, F2 a ds k, p) = (xs, F3 a (k * (readR ds)) [] 1, p)+ go ';' (xs, F2 a [] _, SM _ b c d e) = let p' = SM a b c d e in (xs |> p', F1 [] 1, p')+ go ';' (xs, F2 a ds k, SM _ _ c d e) = let p' = SM a (k * (readR ds)) c d e in (xs |> p', F1 [] 1, p') - go d (xs, F3 a b ds k, p) | isDigit d = (xs, F3 a b (digitToInt d : ds) k, p)- go '-' (xs, F3 a b [] _, p) = (xs, F3 a b [] (-1), p)- go ':' (xs, F3 a b [] _, p@(SM _ _ c _)) = (xs, F4 a b c, p)- go ':' (xs, F3 a b ds k, p) = (xs, F4 a b (k * digits ds), p)- go ';' (xs, F3 a b [] _, SM _ _ c d) = let p' = SM a b c d in (xs |> p', F1 [] 1, p')- go ';' (xs, F3 a b ds k, SM _ _ _ d) = let p' = SM a b (k * digits ds) d in- (xs |> p', F1 [] 1, p')+ go d (xs, F3 a b ds k, p) | isDigit d = (xs, F3 a b (d : ds) k, p)+ go '-' (xs, F3 a b [] _, p) = (xs, F3 a b [] (-1), p)+ go ':' (xs, F3 a b [] _, p@(SM _ _ c _ _)) = (xs, F4 a b c Nothing, p)+ go ':' (xs, F3 a b ds k, p) = (xs, F4 a b (k * (readR ds)) Nothing, p)+ go ';' (xs, F3 a b [] _, SM _ _ c d e) = let p' = SM a b c d e in (xs |> p', F1 [] 1, p')+ go ';' (xs, F3 a b ds k, SM _ _ _ d e) = let p' = SM a b (k * (readR ds)) d e in+ (xs |> p', F1 [] 1, p') - go 'i' (xs, F4 a b c, p) = (xs, F5 (SM a b c JumpInto), p)- go 'o' (xs, F4 a b c, p) = (xs, F5 (SM a b c JumpFrom), p)- go '-' (xs, F4 a b c, p) = (xs, F5 (SM a b c JumpRegular), p)- go ';' (xs, F5 s, _) = (xs |> s, F1 [] 1, s)+ go 'i' (xs, F4 a b c Nothing, p) = (xs, F4 a b c (Just JumpInto), p)+ go 'o' (xs, F4 a b c Nothing, p) = (xs, F4 a b c (Just JumpFrom), p)+ go '-' (xs, F4 a b c Nothing, p) = (xs, F4 a b c (Just JumpRegular), p)+ go ':' (xs, F4 a b c (Just d), p) = (xs, F5 a b c d [], p)+ go ':' (xs, F4 a b c _, p@(SM _ _ _ d _)) = (xs, F5 a b c d [], p)+ go ';' (xs, F4 a b c _, SM _ _ _ d e) = let p' = SM a b c d e in+ (xs |> p', F1 [] 1, p') - go c (xs, _, p) = (xs, error ("srcmap: y u " ++ show c ++ "?!?"), p)+ go d (xs, F5 a b c j ds, p) | isDigit d = (xs, F5 a b c j (d : ds), p)+ go ';' (xs, F5 a b c j [], _) = let p' = SM a b c j (-1) in -- solc <0.6+ (xs |> p', F1 [] 1, p')+ go ';' (xs, F5 a b c j ds, _) = let p' = SM a b c j (readR ds) in -- solc >=0.6+ (xs |> p', F1 [] 1, p') + go c (xs, state, p) = (xs, error ("srcmap: y u " ++ show c ++ " in state" ++ show state ++ "?!?"), p)+ makeSourceCache :: [Text] -> Map Text Value -> IO SourceCache makeSourceCache paths asts = do xs <- mapM (BS.readFile . Text.unpack) paths@@ -214,6 +277,20 @@ let Just (solc, _, _) = readJSON json return (solc ^? ix (path <> ":" <> contract) . creationCode) +solcRuntime :: Text -> Text -> IO (Maybe ByteString)+solcRuntime contract src = do+ (json, path) <- solidity' src+ let Just (solc, _, _) = readJSON json+ return (solc ^? ix (path <> ":" <> contract) . runtimeCode)++functionAbi :: Text -> IO Method+functionAbi f = do+ (json, path) <- solidity' ("contract ABI { function " <> f <> " public {}}")+ let Just (solc, _, _) = readJSON json+ case Map.toList $ solc ^?! ix (path <> ":ABI") . abiMap of+ [(_,b)] -> return b+ _ -> error "hevm internal error: unexpected abi format"+ force :: String -> Maybe a -> a force s = fromMaybe (error s) @@ -244,7 +321,7 @@ _contractAst = fromMaybe (error "JSON lacks abstract syntax trees.")- (preview (ix (Text.split (== ':') s !! 0) . key "AST") asts),+ (preview (ix (head (Text.split (== ':') s)) . key "AST") asts), _constructorInputs = let@@ -284,14 +361,30 @@ (case abi ^?! key "anonymous" . _Bool of True -> Anonymous False -> NotAnonymous)- (map (\y -> ( force "internal error: type" (parseTypeName (y ^?! key "type" . _String))+ (map (\y -> ( force "internal error: type" (parseTypeName' y) , if y ^?! key "indexed" . _Bool then Indexed else NotIndexed )) (toList $ abi ^?! key "inputs" . _Array))- )+ ),+ _storageLayout = mkStorageLayout $ x ^? key "storage-layout" . _String }) +mkStorageLayout :: Maybe Text -> Maybe (Map Text StorageItem)+mkStorageLayout Nothing = Nothing+mkStorageLayout (Just json) = do items <- json ^? key "storage" . _Array+ types <- json ^? key "types"+ Map.fromList <$> mapM+ (\item -> do name <- item ^? key "label" . _String+ offset <- item ^? key "offset" . _Number >>= toBoundedInteger+ slot <- item ^? key "slot" . _String+ typ <- item ^? key "type" . _String+ slotType <- types ^?! key typ ^? key "label" . _String+ return (name, StorageItem (read $ Text.unpack slotType) offset (read $ Text.unpack slot))++ )+ (Vector.toList items)+ signature :: AsValue s => s -> Text signature abi = case abi ^?! key "type" of@@ -305,11 +398,19 @@ ")" ] +-- Helper function to convert the fields to the desired type+parseTypeName' :: AsValue s => s -> Maybe AbiType+parseTypeName' x =+ parseTypeName+ (fromMaybe mempty $ x ^? key "components" . _Array . to parseComponents)+ (x ^?! key "type" . _String)+ where parseComponents = fmap $ snd . parseMethodInput+ -- This actually can also parse a method output! :O-parseMethodInput :: (Show s, AsValue s) => s -> (Text, AbiType)+parseMethodInput :: AsValue s => s -> (Text, AbiType) parseMethodInput x = ( x ^?! key "name" . _String- , force "internal error: method type" (parseTypeName (x ^?! key "type" . _String))+ , force "internal error: method type" (parseTypeName' x) ) toCode :: Text -> ByteString@@ -318,11 +419,11 @@ solidity' :: Text -> IO (Text, Text) solidity' src = withSystemTempFile "hevm.sol" $ \path handle -> do hClose handle- writeFile path ("pragma solidity ^0.5.2;\n" <> src)+ writeFile path ("pragma solidity ^0.6.7;\n" <> src) x <- pack <$> readProcess "solc"- ["--combined-json=bin-runtime,bin,srcmap,srcmap-runtime,abi,ast", path]+ ["--combined-json=bin-runtime,bin,srcmap,srcmap-runtime,abi,ast,storage-layout", path] "" return (x, pack path) @@ -339,14 +440,14 @@ -- as the codehash matches otherwise, we don't care if there is some -- difference there. stripBytecodeMetadata :: ByteString -> ByteString-stripBytecodeMetadata bs =- let (_, b) = BS.breakSubstring bzzrPrefix (BS.reverse bs)- in BS.reverse b--bzzrPrefix :: ByteString-bzzrPrefix =- -- a1 65 "bzzr0" 0x58 0x20- BS.reverse $ BS.pack [0xa1, 0x65, 98, 122, 122, 114, 48, 0x58, 0x20]+stripBytecodeMetadata bc | BS.length cl /= 2 = bc+ | BS.length h >= cl' && (isRight . deserialiseFromBytes decodeTerm $ fromStrict cbor) = bc'+ | otherwise = bc+ where+ l = BS.length bc+ (h, cl) = BS.splitAt (l - 2) bc+ cl' = fromIntegral . runGet getWord16be $ fromStrict cl+ (bc', cbor) = BS.splitAt (BS.length h - cl') h -- | Every node in the AST has an ID, and other nodes reference those -- IDs. This function recurses through the tree looking for objects@@ -366,13 +467,13 @@ astSrcMap :: Map Int Value -> (SrcMap -> Maybe Value) astSrcMap astIds =- \(SM i n f _) -> Map.lookup (i, n, f) tmp+ \(SM i n f _ _) -> Map.lookup (i, n, f) tmp where tmp :: Map (Int, Int, Int) Value tmp =- ( Map.fromList- . catMaybes- . map (\v ->+ Map.fromList+ . mapMaybe+ (\v -> case preview (key "src" . _String) v of Just src -> case map (readMaybe . Text.unpack) (Text.split (== ':') src) of@@ -384,4 +485,3 @@ Nothing) . Map.elems $ astIds- )
@@ -1,20 +1,18 @@ {-# Language GADTs #-}-{-# Language NamedFieldPuns #-}+{-# Language DataKinds #-} module EVM.Stepper ( Action (..)- , Failure (..) , Stepper , exec , execFully- , execFullyOrFail- , decode- , fail+ , runFully , wait+ , ask , evm- , note , entering , enter+ , interpret ) where @@ -28,14 +26,20 @@ import Prelude hiding (fail) -import Control.Monad.Operational (Program, singleton)+import Control.Monad.Operational (Program(..), singleton, view, ProgramViewT(..), ProgramView)+import Control.Monad.State.Strict (runState, liftIO, StateT)+import qualified Control.Monad.State.Class as State+import qualified EVM.Exec+import Control.Lens (use) import Data.Binary.Get (runGetOrFail) import Data.Text (Text)+import EVM.Symbolic (Buffer) -import EVM (EVM, VMResult (VMFailure, VMSuccess), Error (Query), Query)+import EVM (EVM, VM, VMResult (VMFailure, VMSuccess), Error (Query, Choose), Query, Choose) import qualified EVM import EVM.ABI (AbiType, AbiValue, getAbi)+import qualified EVM.Fetch as Fetch import Data.ByteString (ByteString) import qualified Data.ByteString.Lazy as LazyByteString@@ -44,27 +48,20 @@ data Action a where -- | Keep executing until an intermediate result is reached- Exec :: Action VMResult- - -- | Short-circuit with a failure- Fail :: Failure -> Action a- + Exec :: Action VMResult++ -- | Keep executing until an intermediate state is reached+ Run :: Action VM+ -- | Wait for a query to be resolved Wait :: Query -> Action () + -- | Multiple things can happen+ Ask :: Choose -> Action ()+ -- | Embed a VM state transformation EVM :: EVM a -> Action a - -- | Write something to the log or terminal- Note :: Text -> Action ()---- | Some failure raised by a stepper-data Failure- = ContractNotFound- | DecodingError- | VMFailed Error- deriving Show- -- | Type alias for an operational monad of @Action@ type Stepper a = Program Action a @@ -73,42 +70,43 @@ exec :: Stepper VMResult exec = singleton Exec -fail :: Failure -> Stepper a-fail = singleton . Fail+run :: Stepper VM+run = singleton Run wait :: Query -> Stepper () wait = singleton . Wait +ask :: Choose -> Stepper ()+ask = singleton . Ask+ evm :: EVM a -> Stepper a evm = singleton . EVM -note :: Text -> Stepper ()-note = singleton . Note- -- | Run the VM until final result, resolving all queries-execFully :: Stepper (Either Error ByteString)+execFully :: Stepper (Either Error Buffer) execFully = exec >>= \case VMFailure (Query q) -> wait q >> execFully+ VMFailure (Choose q) ->+ ask q >> execFully VMFailure x -> pure (Left x) VMSuccess x -> pure (Right x) -execFullyOrFail :: Stepper ByteString-execFullyOrFail = execFully >>= either (fail . VMFailed) pure---- | Decode a blob as an ABI value, failing if ABI encoding wrong-decode :: AbiType -> ByteString -> Stepper AbiValue-decode abiType bytes =- case runGetOrFail (getAbi abiType) (LazyByteString.fromStrict bytes) of- Right ("", _, x) ->- pure x- Right _ ->- fail DecodingError- Left _ ->- fail DecodingError+-- | Run the VM until its final state+runFully :: Stepper EVM.VM+runFully = do+ vm <- run+ case EVM._result vm of+ Nothing -> error "should not occur"+ Just (VMFailure (Query q)) ->+ wait q >> runFully+ Just (VMFailure (Choose q)) ->+ ask q >> runFully+ Just _ ->+ pure vm entering :: Text -> Stepper a -> Stepper a entering t stepper = do@@ -118,5 +116,31 @@ pure x enter :: Text -> Stepper ()-enter t = do- evm (EVM.pushTrace (EVM.EntryTrace t))+enter t = evm (EVM.pushTrace (EVM.EntryTrace t))++interpret :: Fetch.Fetcher -> Stepper a -> StateT VM IO a+interpret fetcher =+ eval . view++ where+ eval+ :: ProgramView Action a+ -> StateT VM IO a++ eval (Return x) =+ pure x++ eval (action :>>= k) =+ case action of+ Exec ->+ EVM.Exec.exec >>= interpret fetcher . k+ Run ->+ EVM.Exec.run >>= interpret fetcher . k+ Wait q ->+ do m <- liftIO (fetcher q)+ State.state (runState m) >> interpret fetcher (k ())+ Ask _ ->+ error "cannot make choices with this interpreter"+ EVM m -> do+ r <- State.state (runState m)+ interpret fetcher (k r)
@@ -3,21 +3,18 @@ -- Figures out the layout of storage slots for Solidity contracts. import EVM.Dapp (DappInfo, dappAstSrcMap, dappAstIdMap)-import EVM.Solidity (SolcContract, creationSrcmap)-import EVM.ABI (AbiType (..), parseTypeName, abiTypeSolidity)+import EVM.Solidity (SolcContract, creationSrcmap, SlotType(..))+import EVM.ABI (AbiType (..), parseTypeName) import Data.Aeson (Value (Number)) import Data.Aeson.Lens import Control.Lens -import Data.Text (Text, unpack, words)+import Data.Text (Text, unpack, pack, words) import Data.Foldable (toList) import Data.Maybe (fromMaybe, isJust)-import Data.Monoid ((<>))--import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NonEmpty import qualified Data.Sequence as Seq@@ -80,7 +77,7 @@ [ variableName , " (", name, ")" , "\n", " Type: "- , slotTypeSolidity (slotTypeForDeclaration x)+ , pack $ show (slotTypeForDeclaration x) ] Nothing -> error "malformed variable declaration"@@ -98,32 +95,6 @@ nodeIs "VariableDeclaration" x && preview (key "attributes" . key "constant" . _Bool) x /= Just True -data SlotType- -- Note that mapping keys can only be elementary;- -- that excludes arrays, contracts, and mappings.- = StorageMapping (NonEmpty AbiType) AbiType- | StorageValue AbiType- deriving Show--slotTypeSolidity :: SlotType -> Text-slotTypeSolidity =- \case- StorageValue t ->- abiTypeSolidity t- StorageMapping (s NonEmpty.:| ss) t ->- "mapping("- <> abiTypeSolidity s- <> " => "- <> foldr- (\x y ->- "mapping("- <> abiTypeSolidity x- <> " => "- <> y- <> ")")- (abiTypeSolidity t) ss- <> ")"- slotTypeForDeclaration :: Value -> SlotType slotTypeForDeclaration node = case toList <$> preview (key "children" . _Array) node of@@ -165,10 +136,8 @@ , preview (key "attributes" . key "type" . _String) x ) of (Just "ElementaryTypeName", _, Just typeName) ->- case parseTypeName (head (words typeName)) of- Just t -> t- Nothing ->- error ("ungrokked value type: " ++ show typeName)+ fromMaybe (error ("ungrokked value type: " ++ show typeName))+ (parseTypeName mempty (head (words typeName))) (Just "UserDefinedTypeName", _, _) -> AbiAddressType (Just "ArrayTypeName", fmap toList -> Just [t], _)->
@@ -0,0 +1,317 @@+{-# Language DataKinds #-}+{-# Language OverloadedStrings #-}+{-# Language TypeApplications #-}++module EVM.SymExec where+++import Prelude hiding (Word)++import Control.Lens hiding (pre)+import EVM hiding (Query, push)+import EVM.Exec+import qualified EVM.Fetch as Fetch+import EVM.ABI+import EVM.Stepper (Stepper)+import qualified EVM.Stepper as Stepper+import qualified Control.Monad.Operational as Operational+import EVM.Types hiding (Word)+import EVM.Symbolic (litBytes, SymWord(..), sw256, Buffer(..))+import EVM.Concrete (createAddress, Word)+import qualified EVM.FeeSchedule as FeeSchedule+import Data.SBV.Trans.Control+import Data.SBV.Trans hiding (distinct, Word)+import Data.SBV hiding (runSMT, newArray_, addAxiom, distinct, sWord8s, Word)+import Data.Vector (toList, fromList)++import Control.Monad.IO.Class+import qualified Control.Monad.State.Class as State+import Data.ByteString (ByteString, pack)+import qualified Data.ByteString.Lazy as Lazy+import qualified Data.ByteString as BS+import Data.Text (Text, splitOn, unpack)+import Control.Monad.State.Strict (runStateT, runState, StateT, get, put, zipWithM)+import Control.Applicative++-- | Convenience functions for generating large symbolic byte strings+sbytes32, sbytes256, sbytes512, sbytes1024 :: Query ([SWord 8])+sbytes32 = toBytes <$> freshVar_ @ (WordN 256)+sbytes128 = toBytes <$> freshVar_ @ (WordN 1024)+sbytes256 = liftA2 (++) sbytes128 sbytes128+sbytes512 = liftA2 (++) sbytes256 sbytes256+sbytes1024 = liftA2 (++) sbytes512 sbytes512++-- | Abstract calldata argument generation+-- We don't assume input types are restricted to their proper range here;+-- such assumptions should instead be given as preconditions.+-- This could catch some interesting calldata mismanagement errors.+symAbiArg :: AbiType -> Query ([SWord 8], SWord 32)+symAbiArg (AbiUIntType n) | n `mod` 8 == 0 && n <= 256 = do x <- sbytes32+ return (x, 32)+ | otherwise = error "bad type"++symAbiArg (AbiIntType n) | n `mod` 8 == 0 && n <= 256 = do x <- sbytes32+ return (x, 32)+ | otherwise = error "bad type"+symAbiArg AbiBoolType = do x <- sbytes32+ return (x, 32)++symAbiArg AbiAddressType = do x <- sbytes32+ return (x, 32)++symAbiArg (AbiBytesType n) | n <= 32 = do x <- sbytes32+ return (x, 32)+ | otherwise = error "bad type"++-- TODO: is this encoding correct?+symAbiArg (AbiArrayType len typ) =+ do args <- mapM symAbiArg (replicate len typ)+ return (litBytes (encodeAbiValue (AbiUInt 256 (fromIntegral len))) <> (concat $ fst <$> args),+ 32 + (sum $ snd <$> args))++symAbiArg (AbiTupleType tuple) =+ do args <- mapM symAbiArg (toList tuple)+ return (concat $ fst <$> args, sum $ snd <$> args)+symAbiArg n =+ error $ "Unsupported symbolic abiencoding for"+ <> show n+ <> ". Please file an issue at https://github.com/dapphub/dapptools if you really need this."++-- | Generates calldata matching given type signature, optionally specialized+-- with concrete arguments.+-- Any argument given as "<symbolic>" or omitted at the tail of the list are+-- kept symbolic.+symCalldata :: Text -> [AbiType] -> [String] -> Query ([SWord 8], SWord 32)+symCalldata sig typesignature concreteArgs =+ let args = concreteArgs <> replicate (length typesignature - length concreteArgs) "<symbolic>"+ mkArg typ "<symbolic>" = symAbiArg typ+ mkArg typ arg = let n = litBytes . encodeAbiValue $ makeAbiValue typ arg+ in return (n, num (length n))+ sig' = litBytes $ selector sig+ in do calldatas <- zipWithM mkArg typesignature args+ return (sig' <> concat (fst <$> calldatas), 4 + (sum $ snd <$> calldatas))++abstractVM :: Maybe (Text, [AbiType]) -> [String] -> ByteString -> StorageModel -> Query VM+abstractVM typesignature concreteArgs x storagemodel = do+ (cd', cdlen, cdconstraint) <-+ case typesignature of+ Nothing -> do cd <- sbytes256+ len <- freshVar_+ return (cd, len, len .<= 256)+ Just (name, typs) -> do (cd, cdlen) <- symCalldata name typs concreteArgs+ return (cd, cdlen, sTrue)+ symstore <- case storagemodel of+ SymbolicS -> Symbolic <$> freshArray_ Nothing+ InitialS -> Symbolic <$> freshArray_ (Just 0)+ ConcreteS -> return $ Concrete mempty+ c <- SAddr <$> freshVar_+ value' <- sw256 <$> freshVar_+ return $ loadSymVM (RuntimeCode x) symstore storagemodel c value' (SymbolicBuffer cd', cdlen) & over pathConditions ((<>) [cdconstraint])++loadSymVM :: ContractCode -> Storage -> StorageModel -> SAddr -> SymWord -> (Buffer, SWord 32) -> VM+loadSymVM x initStore model addr callvalue' calldata' =+ (makeVm $ VMOpts+ { vmoptContract = contractWithStore x initStore+ , vmoptCalldata = calldata'+ , vmoptValue = callvalue'+ , vmoptAddress = createAddress ethrunAddress 1+ , vmoptCaller = addr+ , vmoptOrigin = ethrunAddress --todo: generalize+ , vmoptCoinbase = 0+ , vmoptNumber = 0+ , vmoptTimestamp = 0+ , vmoptBlockGaslimit = 0+ , vmoptGasprice = 0+ , vmoptDifficulty = 0+ , vmoptGas = 0xffffffffffffffff+ , vmoptGaslimit = 0xffffffffffffffff+ , vmoptMaxCodeSize = 0xffffffff+ , vmoptSchedule = FeeSchedule.istanbul+ , vmoptChainId = 1+ , vmoptCreate = False+ , vmoptStorageModel = model+ }) & set (env . contracts . at (createAddress ethrunAddress 1))+ (Just (contractWithStore x initStore))++-- | Interpreter which explores all paths at+-- | branching points.+-- | returns a list of possible final evm states+interpret+ :: Fetch.Fetcher+ -> Maybe Integer --max iterations+ -> Stepper a+ -> StateT VM IO [a]+interpret fetcher maxIter =+ eval . Operational.view++ where+ eval+ :: Operational.ProgramView Stepper.Action a+ -> StateT VM IO [a]++ eval (Operational.Return x) =+ pure [x]++ eval (action Operational.:>>= k) =+ case action of+ Stepper.Exec ->+ exec >>= interpret fetcher maxIter . k+ Stepper.Run ->+ run >>= interpret fetcher maxIter . k+ Stepper.Ask (EVM.PleaseChoosePath continue) -> do+ vm <- get+ case maxIterationsReached vm maxIter of+ Nothing -> do a <- interpret fetcher maxIter (Stepper.evm (continue True) >>= k)+ put vm+ b <- interpret fetcher maxIter (Stepper.evm (continue False) >>= k)+ return $ a <> b+ Just n -> interpret fetcher maxIter (Stepper.evm (continue (not n)) >>= k)+ Stepper.Wait q ->+ do m <- liftIO (fetcher q)+ interpret fetcher maxIter (Stepper.evm m >>= k)+ Stepper.EVM m ->+ State.state (runState m) >>= interpret fetcher maxIter . k++maxIterationsReached :: VM -> Maybe Integer -> Maybe Bool+maxIterationsReached _ Nothing = Nothing+maxIterationsReached vm (Just maxIter) =+ let codelocation = getCodeLocation vm+ iters = view (iterations . at codelocation . non 0) vm+ in if num maxIter <= iters+ then view (cache . path . at (codelocation, iters - 1)) vm+ else Nothing++type Precondition = VM -> SBool+type Postcondition = (VM, VM) -> SBool++checkAssert :: ContractCode -> Maybe Integer -> Maybe (Text, [AbiType]) -> [String] -> Query (Either (VM, [VM]) VM)+checkAssert c maxIter signature' concreteArgs = verifyContract c maxIter signature' concreteArgs SymbolicS (const sTrue) (Just checkAssertions)++checkAssertions :: Postcondition+checkAssertions (_, out) = case view result out of+ Just (EVM.VMFailure (EVM.UnrecognizedOpcode 254)) -> sFalse+ _ -> sTrue++verifyContract :: ContractCode -> Maybe Integer -> Maybe (Text, [AbiType]) -> [String] -> StorageModel -> Precondition -> Maybe Postcondition -> Query (Either (VM, [VM]) VM)+verifyContract code' maxIter signature' concreteArgs storagemodel pre maybepost = do+ preStateRaw <- abstractVM signature' concreteArgs theCode storagemodel+ -- add the pre condition to the pathconditions to ensure that we are only exploring valid paths+ let preState = over pathConditions ((++) [pre preStateRaw]) preStateRaw+ verify preState maxIter Nothing maybepost+ where theCode = case code' of+ InitCode b -> b+ RuntimeCode b -> b++pruneDeadPaths :: [VM] -> [VM]+pruneDeadPaths =+ filter $ \vm -> case view result vm of+ Just (VMFailure DeadPath) -> False+ _ -> True++-- | Symbolically execute the VM and check all endstates against the postcondition, if available.+-- Returns `Right VM` if the postcondition can be violated, where `VM` is a prestate counterexample,+-- or `Left (VM, [VM])`, a pair of `prestate` and post vm states.+verify :: VM -> Maybe Integer -> Maybe (Fetch.BlockNumber, Text) -> Maybe Postcondition -> Query (Either (VM, [VM]) VM)+verify preState maxIter rpcinfo maybepost = do+ let model = view (env . storageModel) preState+ smtState <- queryState+ results <- io $ fst <$> runStateT (interpret (Fetch.oracle smtState rpcinfo model) maxIter Stepper.runFully) preState+ case maybepost of+ (Just post) -> do+ let livePaths = pruneDeadPaths results+ postC = sOr $ fmap (\postState -> (sAnd (view pathConditions postState)) .&& sNot (post (preState, postState))) livePaths+ -- is there any path which can possibly violate+ -- the postcondition?+ resetAssertions+ constrain postC+ io $ putStrLn "checking postcondition..."+ checkSat >>= \case+ Unk -> do io $ putStrLn "postcondition query timed out"+ return $ Left (preState, livePaths)+ Unsat -> do io $ putStrLn "Q.E.D."+ return $ Left (preState, livePaths)+ Sat -> return $ Right preState++ Nothing -> do io $ putStrLn "Q.E.D."+ return $ Left (preState, pruneDeadPaths results)++-- | Compares two contract runtimes for trace equivalence by running two VMs and comparing the end states.+equivalenceCheck :: ByteString -> ByteString -> Maybe Integer -> Maybe (Text, [AbiType]) -> Query (Either ([VM], [VM]) VM)+equivalenceCheck bytecodeA bytecodeB maxiter signature' = do+ preStateA <- abstractVM signature' [] bytecodeA SymbolicS++ let preself = preStateA ^. state . contract+ precaller = preStateA ^. state . caller+ callvalue' = preStateA ^. state . callvalue+ prestorage = preStateA ^?! env . contracts . ix preself . storage+ (calldata', cdlen) = view (state . calldata) preStateA+ pathconds = view pathConditions preStateA+ preStateB = loadSymVM (RuntimeCode bytecodeB) prestorage SymbolicS precaller callvalue' (calldata', cdlen) & set pathConditions pathconds++ smtState <- queryState+ (aVMs, bVMs) <- both (\x -> io $ fst <$> runStateT (interpret (Fetch.oracle smtState Nothing SymbolicS) maxiter Stepper.runFully) x)+ (preStateA, preStateB)+ -- Check each pair of endstates for equality:+ let differingEndStates = uncurry distinct <$> [(a,b) | a <- pruneDeadPaths aVMs, b <- pruneDeadPaths bVMs]+ distinct a b =+ let (aPath, bPath) = both' (view pathConditions) (a, b)+ (aSelf, bSelf) = both' (view (state . contract)) (a, b)+ (aEnv, bEnv) = both' (view (env . contracts)) (a, b)+ (aResult, bResult) = both' (view result) (a, b)+ (Symbolic aStorage, Symbolic bStorage) = (view storage (aEnv ^?! ix aSelf), view storage (bEnv ^?! ix bSelf))+ differingResults = case (aResult, bResult) of++ (Just (VMSuccess aOut), Just (VMSuccess bOut)) ->+ aOut ./= bOut .|| aStorage ./= bStorage .|| fromBool (aSelf /= bSelf)++ (Just (VMFailure UnexpectedSymbolicArg), _) ->+ error $ "Unexpected symbolic argument at opcode: " <> maybe "??" show (vmOp a) <> ". Not supported (yet!)"++ (_, Just (VMFailure UnexpectedSymbolicArg)) ->+ error $ "Unexpected symbolic argument at opcode: " <> maybe "??" show (vmOp a) <> ". Not supported (yet!)"++ (Just (VMFailure _), Just (VMFailure _)) -> sFalse++ (Just _, Just _) -> sTrue++ _ -> error "Internal error during symbolic execution (should not be possible)"++ in sAnd aPath .&& sAnd bPath .&& differingResults+ -- If there exists a pair of endstates where this is not the case,+ -- the following constraint is satisfiable+ resetAssertions+ constrain $ sOr differingEndStates++ checkSat >>= \case+ Unk -> error "solver said unknown!"+ Sat -> return $ Right preStateA+ Unsat -> return $ Left (pruneDeadPaths aVMs, pruneDeadPaths bVMs)++both' :: (a -> b) -> (a, a) -> (b, b)+both' f (x, y) = (f x, f y)++showCounterexample :: VM -> Maybe (Text, [AbiType]) -> Query ()+showCounterexample vm maybesig = do+ let (calldata', cdlen) = view (EVM.state . EVM.calldata) vm+ S _ cvalue = view (EVM.state . EVM.callvalue) vm+ SAddr caller' = view (EVM.state . EVM.caller) vm+ cdlen' <- num <$> getValue cdlen+ calldatainput <- case calldata' of+ SymbolicBuffer cd -> mapM (getValue.fromSized) (take cdlen' cd) >>= return . pack+ ConcreteBuffer cd -> return $ BS.take cdlen' cd+ callvalue' <- num <$> getValue cvalue+ caller'' <- num <$> getValue caller'+ io $ do+ putStrLn "Calldata:"+ print $ ByteStringS calldatainput++ -- pretty print calldata input if signature is available+ case maybesig of+ Just (name, types) -> putStrLn $ unpack (head (splitOn "(" name)) +++ show (decodeAbiValue (AbiTupleType (fromList types)) $ Lazy.fromStrict (BS.drop 4 calldatainput))+ Nothing -> return ()++ putStrLn "Caller:"+ print (Addr caller'')+ putStrLn "Callvalue:"+ print callvalue'
@@ -0,0 +1,300 @@+{-# Language NamedFieldPuns #-}+{-# Language DataKinds #-}+{-# Language OverloadedStrings #-}+{-# Language TypeApplications #-}++module EVM.Symbolic where++import Prelude hiding (Word)+import qualified Data.ByteString as BS+import Data.ByteString (ByteString)+import Control.Lens hiding (op, (:<), (|>), (.>))+import Data.Maybe (fromMaybe, fromJust)++import EVM.Types+import EVM.Concrete (Word (..), Whiff(..))+import qualified EVM.Concrete as Concrete+import Data.SBV hiding (runSMT, newArray_, addAxiom, Word)+++-- | Symbolic words of 256 bits, possibly annotated with additional+-- "insightful" information+data SymWord = S Whiff (SWord 256)++-- | Convenience functions transporting between the concrete and symbolic realm+sw256 :: SWord 256 -> SymWord+sw256 = S Dull++litWord :: Word -> (SymWord)+litWord (C whiff a) = S whiff (literal $ toSizzle a)++w256lit :: W256 -> SymWord+w256lit = S Dull . literal . toSizzle++litAddr :: Addr -> SAddr+litAddr = SAddr . literal . toSizzle++litBytes :: ByteString -> [SWord 8]+litBytes bs = fmap (toSized . literal) (BS.unpack bs)++maybeLitWord :: SymWord -> Maybe Word+maybeLitWord (S whiff a) = fmap (C whiff . fromSizzle) (unliteral a)++maybeLitAddr :: SAddr -> Maybe Addr+maybeLitAddr (SAddr a) = fmap fromSizzle (unliteral a)++maybeLitBytes :: [SWord 8] -> Maybe ByteString+maybeLitBytes xs = fmap (\x -> BS.pack (fmap fromSized x)) (mapM unliteral xs)++-- | Note: these forms are crude and in general,+-- the continuation passing style `forceConcrete`+-- alternatives should be prefered for better error+-- handling when used during EVM execution++forceLit :: SymWord -> Word+forceLit (S whiff a) = case unliteral a of+ Just c -> C whiff (fromSizzle c)+ Nothing -> error "unexpected symbolic argument"++forceLitBytes :: [SWord 8] -> ByteString+forceLitBytes = BS.pack . fmap (fromSized . fromJust . unliteral)+++-- | Arithmetic operations on SymWord++sdiv :: SymWord -> SymWord -> SymWord+sdiv (S _ x) (S _ y) = let sx, sy :: SInt 256+ sx = sFromIntegral x+ sy = sFromIntegral y+ in sw256 $ sFromIntegral (sx `sQuot` sy)++smod :: SymWord -> SymWord -> SymWord+smod (S _ x) (S _ y) = let sx, sy :: SInt 256+ sx = sFromIntegral x+ sy = sFromIntegral y+ in sw256 $ ite (y .== 0) 0 (sFromIntegral (sx `sRem` sy))++addmod :: SymWord -> SymWord -> SymWord -> SymWord+addmod (S _ x) (S _ y) (S _ z) = let to512 :: SWord 256 -> SWord 512+ to512 = sFromIntegral+ in sw256 $ sFromIntegral $ ((to512 x) + (to512 y)) `sMod` (to512 z)++mulmod :: SymWord -> SymWord -> SymWord -> SymWord+mulmod (S _ x) (S _ y) (S _ z) = let to512 :: SWord 256 -> SWord 512+ to512 = sFromIntegral+ in sw256 $ sFromIntegral $ ((to512 x) * (to512 y)) `sMod` (to512 z)++slt :: SymWord -> SymWord -> SymWord+slt (S _ x) (S _ y) =+ sw256 $ ite (sFromIntegral x .< (sFromIntegral y :: (SInt 256))) 1 0++sgt :: SymWord -> SymWord -> SymWord+sgt (S _ x) (S _ y) =+ sw256 $ ite (sFromIntegral x .> (sFromIntegral y :: (SInt 256))) 1 0++-- | Operations over symbolic memory (list of symbolic bytes)+swordAt :: Int -> [SWord 8] -> SymWord+swordAt i bs = sw256 . fromBytes $ truncpad 32 $ drop i bs++readByteOrZero' :: Int -> [SWord 8] -> SWord 8+readByteOrZero' i bs = fromMaybe 0 (bs ^? ix i)++sliceWithZero' :: Int -> Int -> [SWord 8] -> [SWord 8]+sliceWithZero' o s m = truncpad s $ drop o m++writeMemory' :: [SWord 8] -> Word -> Word -> Word -> [SWord 8] -> [SWord 8]+writeMemory' bs1 (C _ n) (C _ src) (C _ dst) bs0 =+ let+ (a, b) = splitAt (num dst) bs0+ a' = replicate (num dst - length a) 0+ c = if src > num (length bs1)+ then replicate (num n) 0+ else sliceWithZero' (num src) (num n) bs1+ b' = drop (num (n)) b+ in+ a <> a' <> c <> b'++readMemoryWord' :: Word -> [SWord 8] -> SymWord+readMemoryWord' (C _ i) m = sw256 $ fromBytes $ truncpad 32 (drop (num i) m)++readMemoryWord32' :: Word -> [SWord 8] -> SWord 32+readMemoryWord32' (C _ i) m = fromBytes $ truncpad 4 (drop (num i) m)++setMemoryWord' :: Word -> SymWord -> [SWord 8] -> [SWord 8]+setMemoryWord' (C _ i) (S _ x) =+ writeMemory' (toBytes x) 32 0 (num i)++setMemoryByte' :: Word -> SWord 8 -> [SWord 8] -> [SWord 8]+setMemoryByte' (C _ i) x =+ writeMemory' [x] 1 0 (num i)++readSWord' :: Word -> [SWord 8] -> SymWord+readSWord' (C _ i) x =+ if i > num (length x)+ then 0+ else swordAt (num i) x+++select' :: (Ord b, Num b, SymVal b, Mergeable a) => [a] -> a -> SBV b -> a+select' xs err ind = walk xs ind err+ where walk [] _ acc = acc+ walk (e:es) i acc = walk es (i-1) (ite (i .== 0) e acc)++-- Generates a ridiculously large set of constraints (roughly 25k) when+-- the index is symbolic, but it still seems (kind of) manageable+-- for the solvers.+readSWordWithBound :: SWord 32 -> Buffer -> SWord 32 -> SymWord+readSWordWithBound ind (SymbolicBuffer xs) bound =+ let boundedList = [ite (i .<= bound) x 0 | (x, i) <- zip xs [1..]]+ in sw256 . fromBytes $ [select' boundedList 0 (ind + j) | j <- [0..31]]+readSWordWithBound ind (ConcreteBuffer xs) bound =+ case fromSized <$> unliteral ind of+ Nothing -> readSWordWithBound ind (SymbolicBuffer (litBytes xs)) bound+ Just x' -> + -- INVARIANT: bound should always be length xs for concrete bytes+ -- so we should be able to safely ignore it here+ litWord $ Concrete.readMemoryWord (num x') xs+++-- | Operations over buffers (concrete or symbolic)++-- | A buffer is a list of bytes. For concrete execution, this is simply `ByteString`.+-- In symbolic settings, it is a list of symbolic bitvectors of size 8.+data Buffer+ = ConcreteBuffer ByteString+ | SymbolicBuffer [SWord 8]+ deriving (Show)++instance Semigroup Buffer where+ ConcreteBuffer a <> ConcreteBuffer b = ConcreteBuffer (a <> b)+ ConcreteBuffer a <> SymbolicBuffer b = SymbolicBuffer (litBytes a <> b)+ SymbolicBuffer a <> ConcreteBuffer b = SymbolicBuffer (a <> litBytes b)+ SymbolicBuffer a <> SymbolicBuffer b = SymbolicBuffer (a <> b)++instance Monoid Buffer where+ mempty = ConcreteBuffer mempty++instance EqSymbolic Buffer where+ ConcreteBuffer a .== ConcreteBuffer b = literal (a == b)+ ConcreteBuffer a .== SymbolicBuffer b = litBytes a .== b+ SymbolicBuffer a .== ConcreteBuffer b = a .== litBytes b+ SymbolicBuffer a .== SymbolicBuffer b = a .== b+++-- a whole foldable instance seems overkill, but length is always good to have!+len :: Buffer -> Int+len (SymbolicBuffer bs) = length bs+len (ConcreteBuffer bs) = BS.length bs++grab :: Int -> Buffer -> Buffer+grab n (SymbolicBuffer bs) = SymbolicBuffer $ take n bs+grab n (ConcreteBuffer bs) = ConcreteBuffer $ BS.take n bs++ditch :: Int -> Buffer -> Buffer+ditch n (SymbolicBuffer bs) = SymbolicBuffer $ drop n bs+ditch n (ConcreteBuffer bs) = ConcreteBuffer $ BS.drop n bs++readByteOrZero :: Int -> Buffer -> SWord 8+readByteOrZero i (SymbolicBuffer bs) = readByteOrZero' i bs+readByteOrZero i (ConcreteBuffer bs) = num $ Concrete.readByteOrZero i bs++sliceWithZero :: Int -> Int -> Buffer -> Buffer+sliceWithZero o s (SymbolicBuffer m) = SymbolicBuffer (sliceWithZero' o s m)+sliceWithZero o s (ConcreteBuffer m) = ConcreteBuffer (Concrete.byteStringSliceWithDefaultZeroes o s m)++writeMemory :: Buffer -> Word -> Word -> Word -> Buffer -> Buffer+writeMemory (ConcreteBuffer bs1) n src dst (ConcreteBuffer bs0) =+ ConcreteBuffer (Concrete.writeMemory bs1 n src dst bs0)+writeMemory (ConcreteBuffer bs1) n src dst (SymbolicBuffer bs0) =+ SymbolicBuffer (writeMemory' (litBytes bs1) n src dst bs0)+writeMemory (SymbolicBuffer bs1) n src dst (ConcreteBuffer bs0) =+ SymbolicBuffer (writeMemory' bs1 n src dst (litBytes bs0))+writeMemory (SymbolicBuffer bs1) n src dst (SymbolicBuffer bs0) =+ SymbolicBuffer (writeMemory' bs1 n src dst bs0)++readMemoryWord :: Word -> Buffer -> SymWord+readMemoryWord i (SymbolicBuffer m) = readMemoryWord' i m+readMemoryWord i (ConcreteBuffer m) = litWord $ Concrete.readMemoryWord i m++readMemoryWord32 :: Word -> Buffer -> SWord 32+readMemoryWord32 i (SymbolicBuffer m) = readMemoryWord32' i m+readMemoryWord32 i (ConcreteBuffer m) = num $ Concrete.readMemoryWord32 i m++setMemoryWord :: Word -> SymWord -> Buffer -> Buffer+setMemoryWord i x (SymbolicBuffer z) = SymbolicBuffer $ setMemoryWord' i x z+setMemoryWord i x (ConcreteBuffer z) = case maybeLitWord x of+ Just x' -> ConcreteBuffer $ Concrete.setMemoryWord i x' z+ Nothing -> SymbolicBuffer $ setMemoryWord' i x (litBytes z)++setMemoryByte :: Word -> SWord 8 -> Buffer -> Buffer+setMemoryByte i x (SymbolicBuffer m) = SymbolicBuffer $ setMemoryByte' i x m+setMemoryByte i x (ConcreteBuffer m) = case fromSized <$> unliteral x of+ Nothing -> SymbolicBuffer $ setMemoryByte' i x (litBytes m)+ Just x' -> ConcreteBuffer $ Concrete.setMemoryByte i x' m++readSWord :: Word -> Buffer -> SymWord+readSWord i (SymbolicBuffer x) = readSWord' i x+readSWord i (ConcreteBuffer x) = num $ Concrete.readMemoryWord i x++-- | Custom instances for SymWord, many of which have direct+-- analogues for concrete words defined in Concrete.hs++instance Show SymWord where+ show s@(S Dull _) = case maybeLitWord s of+ Nothing -> "<symbolic>"+ Just w -> show w+ show (S (Var var) x) = var ++ ": " ++ show x+ show (S (InfixBinOp symbol x y) z) = show x ++ symbol ++ show y ++ ": " ++ show z+ show (S (BinOp symbol x y) z) = symbol ++ show x ++ show y ++ ": " ++ show z+ show (S (UnOp symbol x) z) = symbol ++ show x ++ ": " ++ show z+ show (S whiff x) = show whiff ++ ": " ++ show x++instance EqSymbolic SymWord where+ (.==) (S _ x) (S _ y) = x .== y++instance Num SymWord where+ (S _ x) + (S _ y) = sw256 (x + y)+ (S _ x) * (S _ y) = sw256 (x * y)+ abs (S _ x) = sw256 (abs x)+ signum (S _ x) = sw256 (signum x)+ fromInteger x = sw256 (fromInteger x)+ negate (S _ x) = sw256 (negate x)++instance Bits SymWord where+ (S _ x) .&. (S _ y) = sw256 (x .&. y)+ (S _ x) .|. (S _ y) = sw256 (x .|. y)+ (S _ x) `xor` (S _ y) = sw256 (x `xor` y)+ complement (S _ x) = sw256 (complement x)+ shift (S _ x) i = sw256 (shift x i)+ rotate (S _ x) i = sw256 (rotate x i)+ bitSize (S _ x) = bitSize x+ bitSizeMaybe (S _ x) = bitSizeMaybe x+ isSigned (S _ x) = isSigned x+ testBit (S _ x) i = testBit x i+ bit i = sw256 (bit i)+ popCount (S _ x) = popCount x++instance SDivisible SymWord where+ sQuotRem (S _ x) (S _ y) = let (a, b) = x `sQuotRem` y+ in (sw256 a, sw256 b)+ sDivMod (S _ x) (S _ y) = let (a, b) = x `sDivMod` y+ in (sw256 a, sw256 b)++instance Mergeable SymWord where+ symbolicMerge a b (S _ x) (S _ y) = sw256 $ symbolicMerge a b x y+ select xs (S _ x) b = let ys = fmap (\(S _ y) -> y) xs+ in sw256 $ select ys x b++instance Bounded SymWord where+ minBound = sw256 minBound+ maxBound = sw256 maxBound++instance Eq SymWord where+ (S _ x) == (S _ y) = x == y++instance Enum SymWord where+ toEnum i = sw256 (toEnum i)+ fromEnum (S _ x) = fromEnum x++instance OrdSymbolic SymWord where+ (.<) (S _ x) (S _ y) = (.<) x y
@@ -1,9 +1,9 @@ {-# Language TemplateHaskell #-} {-# Language ImplicitParams #-}-+{-# Language DataKinds #-} module EVM.TTY where -import Prelude hiding (Word)+import Prelude hiding (lookup, Word) import Brick import Brick.Widgets.Border@@ -11,22 +11,21 @@ import Brick.Widgets.List import EVM-import EVM.ABI (abiTypeSolidity)-import EVM.Concrete (Word (C))+import EVM.ABI (abiTypeSolidity, decodeAbiValue, AbiType(..), emptyAbi)+import EVM.Symbolic (SymWord(..), Buffer(..))+import EVM.SymExec (maxIterationsReached) import EVM.Dapp (DappInfo, dappInfo)-import EVM.Dapp (dappUnitTests, dappSolcByName, dappSolcByHash, dappSources)+import EVM.Dapp (dappUnitTests, unitTestMethods, dappSolcByName, dappSolcByHash, dappSources) import EVM.Dapp (dappAstSrcMap) import EVM.Debug import EVM.Format (Signedness (..), showDec, showWordExact)-import EVM.Format (showTraceTree)-import EVM.Format (contractNamePart, contractPathPart)+import EVM.Format (contractNamePart, contractPathPart, showTraceTree) import EVM.Hexdump (prettyHex) import EVM.Op import EVM.Solidity import EVM.Types hiding (padRight) import EVM.UnitTest (UnitTestOptions (..))-import EVM.UnitTest (initialUnitTestVm)-import EVM.UnitTest (initializeUnitTest, runUnitTest)+import EVM.UnitTest (initialUnitTestVm, initializeUnitTest, runUnitTest) import EVM.StorageLayout import EVM.Stepper (Stepper)@@ -34,62 +33,65 @@ import qualified Control.Monad.Operational as Operational import EVM.Fetch (Fetcher)-import qualified EVM.Fetch as Fetch import Control.Lens import Control.Monad.State.Strict hiding (state) import Data.Aeson.Lens import Data.ByteString (ByteString)-import Data.Maybe (fromJust, fromMaybe)+import Data.Maybe (isJust, fromJust, fromMaybe)+import Data.Map (Map, insert, lookupLT, singleton) import Data.Monoid ((<>)) import Data.Text (Text, unpack, pack) import Data.Text.Encoding (decodeUtf8)-import Data.List (sort)-import Numeric (showHex)+import Data.List (sort, lookup)+import Data.Version (showVersion)+import Data.SBV hiding (solver) import qualified Data.ByteString as BS import qualified Data.Map as Map import qualified Data.Text as Text import qualified Data.Vector as Vec import qualified Data.Vector.Storable as SVec-import qualified Graphics.Vty as Vty+import qualified Graphics.Vty as V import qualified System.Console.Haskeline as Readline import qualified EVM.TTYCenteredList as Centered +import qualified Paths_hevm as Paths+ data Name = AbiPane | StackPane | BytecodePane | TracePane | SolidityPane- | SolidityViewport | TestPickerPane | BrowserPane+ | Pager deriving (Eq, Show, Ord) type UiWidget = Widget Name data UiVmState = UiVmState- { _uiVm :: VM- , _uiVmNextStep :: Stepper ()- , _uiVmStackList :: List Name (Int, Word)- , _uiVmBytecodeList :: List Name (Int, Op)- , _uiVmTraceList :: List Name Text- , _uiVmSolidityList :: List Name (Int, ByteString)- , _uiVmSolc :: Maybe SolcContract- , _uiVmDapp :: Maybe DappInfo- , _uiVmStepCount :: Int- , _uiVmFirstState :: UiVmState- , _uiVmMessage :: Maybe String- , _uiVmNotes :: [String]- , _uiVmShowMemory :: Bool+ { _uiVm :: VM+ , _uiStep :: Int+ , _uiSnapshots :: Map Int (VM, Stepper ())+ , _uiStepper :: Stepper ()+ , _uiStackList :: List Name (Int, (SymWord))+ , _uiBytecodeList :: List Name (Int, Op)+ , _uiTraceList :: List Name Text+ , _uiSolidityList :: List Name (Int, ByteString)+ , _uiMessage :: Maybe String+ , _uiShowMemory :: Bool+ , _uiSolc :: Maybe SolcContract+ , _uiTestOpts :: UnitTestOptions } data UiTestPickerState = UiTestPickerState { _testPickerList :: List Name (Text, Text) , _testPickerDapp :: DappInfo+ , _testOpts :: UnitTestOptions } data UiBrowserState = UiBrowserState@@ -98,37 +100,41 @@ } data UiState- = UiVmScreen UiVmState- | UiVmBrowserScreen UiBrowserState- | UiTestPickerScreen UiTestPickerState+ = ViewVm UiVmState+ | ViewContracts UiBrowserState+ | ViewPicker UiTestPickerState+ | ViewHelp UiVmState makeLenses ''UiVmState makeLenses ''UiTestPickerState makeLenses ''UiBrowserState makePrisms ''UiState +-- caching VM states lets us backstep efficiently+snapshotInterval :: Int+snapshotInterval = 50+ type Pred a = a -> Bool data StepMode- = StepOne -- ^ Finish after one opcode step- | StepMany !Int -- ^ Run a specific number of steps- | StepNone -- ^ Finish before the next opcode- | StepUntil (Pred VM) -- ^ Finish when a VM predicate holds+ = Step !Int -- ^ Run a specific number of steps+ | StepUntil (Pred VM) -- ^ Finish when a VM predicate holds -- | Each step command in the terminal should finish immediately -- with one of these outcomes.-data StepOutcome a- = Returned a -- ^ Program finished- | Stepped (Stepper a) -- ^ Took one step; more steps to go- | Blocked (IO (Stepper a)) -- ^ Came across blocking request+data Continuation a+ = Stopped a -- ^ Program finished+ | Continue (Stepper a) -- ^ Took one step; more steps to go + -- | This turns a @Stepper@ into a state action usable -- from within the TTY loop, yielding a @StepOutcome@ depending on the @StepMode@. interpret- :: (?fetcher :: Fetcher)+ :: (?fetcher :: Fetcher+ , ?maxIter :: Maybe Integer) => StepMode -> Stepper a- -> State UiVmState (StepOutcome a)+ -> StateT UiVmState IO (Continuation a) interpret mode = -- Like the similar interpreters in @EVM.UnitTest@ and @EVM.VMTest@,@@ -138,10 +144,10 @@ where eval :: Operational.ProgramView Stepper.Action a- -> State UiVmState (StepOutcome a)+ -> StateT UiVmState IO (Continuation a) eval (Operational.Return x) =- pure (Returned x)+ pure (Stopped x) eval (action Operational.:>>= k) = case action of@@ -149,138 +155,121 @@ -- Stepper wants to keep executing? Stepper.Exec -> do - let- -- When pausing during exec, we should later restart- -- the exec with the same continuation.- restart = Stepper.exec >>= k-- case mode of- StepNone ->- -- We come here when we've continued while stepping,- -- either from a query or from a return;- -- we should pause here and wait for the user.- pure (Stepped (Operational.singleton action >>= k))-- StepOne -> do- -- Run an instruction- modify stepOneOpcode-- use (uiVm . result) >>= \case- Nothing ->- -- If instructions remain, then pause & await user.- pure (Stepped restart)- Just r ->- -- If returning, proceed directly the continuation,- -- but stopping before the next instruction.- interpret StepNone (k r)-- StepMany 0 -> do- -- Finish the continuation until the next instruction;- -- then, pause & await user.- interpret StepNone restart+ -- Have we reached the final result of this action?+ use (uiVm . result) >>= \case+ Just r ->+ -- Yes, proceed with the next action.+ interpret mode (k r)+ Nothing -> do+ -- No, keep performing the current action+ let restart = Stepper.exec >>= k - StepMany i -> do- -- Run one instruction.- interpret StepOne restart >>=- \case- Stepped stepper ->- interpret (StepMany (i - 1)) stepper+ case mode of+ Step 0 -> do+ -- We come here when we've continued while stepping,+ -- either from a query or from a return;+ -- we should pause here and wait for the user.+ pure (Continue restart) - -- This shouldn't happen, because re-stepping needs- -- to avoid blocking and halting.- r -> pure r+ Step i -> do+ -- Run one instruction and recurse+ stepOneOpcode restart+ interpret (Step (i - 1)) restart - StepUntil p -> do- vm <- use uiVm- case p vm of- True ->- interpret StepNone restart- False ->- interpret StepOne restart >>=- \case- Stepped stepper ->- interpret (StepUntil p) stepper+ StepUntil p -> do+ vm <- use uiVm+ case p vm of+ True ->+ interpret (Step 0) restart+ False -> do+ -- Run one instruction and recurse+ stepOneOpcode restart+ interpret (StepUntil p) restart - -- This means that if we hit a blocking query- -- or a return, we pause despite the predicate.- --- -- This could be fixed if we allowed query I/O- -- here, instead of only in the TTY event loop;- -- let's do it later.- r -> pure r+ -- Stepper is waiting for user input from a query+ Stepper.Ask (EVM.PleaseChoosePath cont) -> do+ -- ensure we aren't stepping past max iterations+ vm <- use uiVm+ case maxIterationsReached vm ?maxIter of+ Nothing -> pure $ Continue (k ())+ Just n -> interpret mode (Stepper.evm (cont (not n)) >>= k) -- Stepper wants to make a query and wait for the results? Stepper.Wait q -> do- -- Tell the TTY to run an I/O action to produce the next stepper.- pure . Blocked $ do- -- First run the fetcher, getting a VM state transition back.- m <- ?fetcher q- -- Join that transition with the stepper script's continuation.- pure (Stepper.evm m >> k ())+ do m <- liftIO (?fetcher q)+ interpret mode (Stepper.evm m >>= k) -- Stepper wants to modify the VM. Stepper.EVM m -> do- vm0 <- use uiVm- let (r, vm1) = runState m vm0- modify (flip updateUiVmState vm1)- interpret mode (k r)-- -- Stepper wants to emit a message.- Stepper.Note s -> do- assign uiVmMessage (Just (unpack s))- modifying uiVmNotes (unpack s :)- interpret mode (k ())-- -- Stepper wants to exit because of a failure.- Stepper.Fail e ->- error ("VM error: " ++ show e)+ vm <- use uiVm+ let (r, vm1) = runState m vm+ assign uiVm vm1+ interpret mode (Stepper.exec >> (k r)) isUnitTestContract :: Text -> DappInfo -> Bool isUnitTestContract name dapp = elem name (map fst (view dappUnitTests dapp)) -mkVty :: IO Vty.Vty+mkVty :: IO V.Vty mkVty = do- vty <- Vty.mkVty Vty.defaultConfig- Vty.setMode (Vty.outputIface vty) Vty.BracketedPaste True+ vty <- V.mkVty V.defaultConfig+ V.setMode (V.outputIface vty) V.BracketedPaste True return vty -runFromVM :: VM -> IO VM-runFromVM vm = do- let- ui0 = UiVmState- { _uiVm = vm- , _uiVmNextStep =- void Stepper.execFully >> Stepper.evm finalize- , _uiVmStackList = undefined- , _uiVmBytecodeList = undefined- , _uiVmTraceList = undefined- , _uiVmSolidityList = undefined- , _uiVmSolc = Nothing- , _uiVmDapp = Nothing- , _uiVmStepCount = 0- , _uiVmFirstState = undefined- , _uiVmMessage = Just "Executing EVM code"- , _uiVmNotes = []- , _uiVmShowMemory = False- }- ui1 = updateUiVmState ui0 vm & set uiVmFirstState ui1+runFromVM :: Maybe Integer -> DappInfo -> (Query -> IO (EVM ())) -> VM -> IO VM+runFromVM maxIter' dappinfo oracle' vm = do - testOpts = UnitTestOptions- { oracle = Fetch.zero- , verbose = False+ let+ opts = UnitTestOptions+ { oracle = oracle'+ , verbose = Nothing+ , maxIter = maxIter' , match = ""+ , fuzzRuns = 1+ , replay = error "irrelevant" , vmModifier = id , testParams = error "irrelevant"+ , dapp = dappinfo }+ ui0 = initUiVmState vm opts (void Stepper.execFully) - ui2 <- customMain mkVty Nothing (app testOpts) (UiVmScreen ui1)+ v <- mkVty+ ui2 <- customMain v mkVty Nothing (app opts) (ViewVm ui0) case ui2 of- UiVmScreen ui -> return (view uiVm ui)+ ViewVm ui -> return (view uiVm ui) _ -> error "internal error: customMain returned prematurely" ++initUiVmState :: VM -> UnitTestOptions -> Stepper () -> UiVmState+initUiVmState vm0 opts script =+ renderVm $+ UiVmState+ { _uiVm = vm0+ , _uiStepper = script+ , _uiStackList = undefined+ , _uiBytecodeList = undefined+ , _uiTraceList = undefined+ , _uiSolidityList = undefined+ , _uiSolc = currentSolc (dapp opts) vm0+ , _uiStep = 0+ , _uiSnapshots = singleton 0 (vm0, script)+ , _uiMessage = Just "Creating unit test contract"+ , _uiShowMemory = False+ , _uiTestOpts = opts+ }+++-- filters out fuzztests, unless they have+-- explicitly been given an argument by `replay`+concreteTests :: UnitTestOptions -> (Text, [(Text, [AbiType])]) -> [(Text, Text)]+concreteTests UnitTestOptions{..} (contractname, tests) = case replay of+ Nothing -> [(contractname, fst x) | x <- tests,+ null $ snd x]+ Just (sig, _) -> [(contractname, fst x) | x <- tests,+ null (snd x) || fst x == sig]+ main :: UnitTestOptions -> FilePath -> FilePath -> IO ()-main opts root jsonFilePath = do+main opts root jsonFilePath = readSolc jsonFilePath >>= \case Nothing ->@@ -288,220 +277,315 @@ Just (contractMap, sourceCache) -> do let dapp = dappInfo root contractMap sourceCache- ui = UiTestPickerScreen $ UiTestPickerState+ ui = ViewPicker $ UiTestPickerState { _testPickerList = list TestPickerPane (Vec.fromList (concatMap- (\(a, xs) -> [(a, x) | x <- xs])+ (concreteTests opts) (view dappUnitTests dapp))) 1 , _testPickerDapp = dapp+ , _testOpts = opts }-- _ <- customMain mkVty Nothing (app opts) (ui :: UiState)+ v <- mkVty+ _ <- customMain v mkVty Nothing (app opts) (ui :: UiState) return () --- ^ Specifies whether to do I/O blocking or VM halting while stepping.--- When we step backwards, we don't want to allow those things.-data StepPolicy- = StepNormally -- ^ Allow blocking and returning- | StepTimidly -- ^ Forbid blocking and returning- takeStep- :: (?fetcher :: Fetcher)+ :: (?fetcher :: Fetcher+ ,?maxIter :: Maybe Integer) => UiVmState- -> StepPolicy -> StepMode -> EventM n (Next UiState)-takeStep ui policy mode = do- let m = interpret mode (view uiVmNextStep ui)+takeStep ui mode =+ liftIO nxt >>= \case+ (Stopped (), ui') ->+ continue (ViewVm ui')+ (Continue steps, ui') -> do+ continue (ViewVm (ui' & set uiStepper steps))+ where+ m = interpret mode (view uiStepper ui)+ nxt = runStateT (m <* modify renderVm) ui - case runState (m <* modify renderVm) ui of+appEvent+ :: (?fetcher::Fetcher, ?maxIter :: Maybe Integer) =>+ UiState ->+ BrickEvent Name e ->+ EventM Name (Next UiState) - (Stepped stepper, ui') -> do- continue (UiVmScreen (ui' & set uiVmNextStep stepper))+-- Contracts: Down - list down+appEvent (ViewContracts s) (VtyEvent e@(V.EvKey V.KDown [])) = do+ s' <- handleEventLensed s+ browserContractList+ handleListEvent+ e+ continue (ViewContracts s') - (Blocked blocker, ui') ->- case policy of- StepNormally -> do- stepper <- liftIO blocker- takeStep- (execState (assign uiVmNextStep stepper) ui')- StepNormally StepNone+-- Contracts: Up - list up+appEvent (ViewContracts s) (VtyEvent e@(V.EvKey V.KUp [])) = do+ s' <- handleEventLensed s+ browserContractList+ handleListEvent+ e+ continue (ViewContracts s') - StepTimidly ->- error "step blocked unexpectedly"+-- Vm Overview: Esc - return to test picker or exit+appEvent st@(ViewVm s) (VtyEvent (V.EvKey V.KEsc [])) =+ let opts = view uiTestOpts s+ dapp' = dapp (view uiTestOpts s)+ tests = concatMap+ (concreteTests opts)+ (view dappUnitTests dapp')+ in case tests of+ [] -> halt st+ ts ->+ continue . ViewPicker $+ UiTestPickerState+ { _testPickerList =+ list+ TestPickerPane+ (Vec.fromList+ ts)+ 1+ , _testPickerDapp = dapp'+ , _testOpts = opts+ } - (Returned (), ui') ->- case policy of- StepNormally ->- halt (UiVmScreen ui')- StepTimidly ->- error "step halted unexpectedly"+-- Vm Overview: Enter - open contracts view+appEvent (ViewVm s) (VtyEvent (V.EvKey V.KEnter [])) =+ continue . ViewContracts $ UiBrowserState+ { _browserContractList =+ list+ BrowserPane+ (Vec.fromList (Map.toList (view (uiVm . env . contracts) s)))+ 2+ , _browserVm = s+ } -app :: UnitTestOptions -> App UiState () Name-app opts =- let ?fetcher = oracle opts- in App- { appDraw = drawUi- , appChooseCursor = neverShowCursor- , appHandleEvent = \ui e ->+-- Vm Overview: m - toggle memory pane+appEvent (ViewVm s) (VtyEvent (V.EvKey (V.KChar 'm') [])) =+ continue (ViewVm (over uiShowMemory not s)) - case (ui, e) of- (UiVmBrowserScreen s, VtyEvent (Vty.EvKey Vty.KEsc [])) ->- continue (UiVmScreen (view browserVm s))+-- Vm Overview: h - open help view+appEvent (ViewVm s) (VtyEvent (V.EvKey (V.KChar 'h') []))+ = continue . ViewHelp $ s - (UiVmBrowserScreen s, VtyEvent e'@(Vty.EvKey Vty.KDown [])) -> do- s' <- handleEventLensed s- browserContractList- handleListEvent- e'- continue (UiVmBrowserScreen s')+-- Vm Overview: spacebar - read input+appEvent (ViewVm s) (VtyEvent (V.EvKey (V.KChar ' ') [])) =+ let+ loop = do+ Readline.getInputLine "% " >>= \case+ Just hey -> Readline.outputStrLn hey+ Nothing -> pure ()+ Readline.getInputLine "% " >>= \case+ Just hey' -> Readline.outputStrLn hey'+ Nothing -> pure ()+ return (ViewVm s)+ in+ suspendAndResume $+ Readline.runInputT Readline.defaultSettings loop - (UiVmBrowserScreen s, VtyEvent e'@(Vty.EvKey Vty.KUp [])) -> do- s' <- handleEventLensed s- browserContractList- handleListEvent- e'- continue (UiVmBrowserScreen s')+-- Vm Overview: n - step+appEvent (ViewVm s) (VtyEvent (V.EvKey (V.KChar 'n') [])) =+ case view (uiVm . result) s of+ Just _ -> continue (ViewVm s)+ _ -> takeStep s (Step 1) - (_, VtyEvent (Vty.EvKey Vty.KEsc [])) ->- halt ui+-- Vm Overview: N - step+appEvent (ViewVm s) (VtyEvent (V.EvKey (V.KChar 'N') [])) =+ takeStep s+ (StepUntil (isNextSourcePosition s)) - (UiVmScreen s, VtyEvent (Vty.EvKey Vty.KEnter [])) ->- continue . UiVmBrowserScreen $ UiBrowserState- { _browserContractList =- list- BrowserPane- (Vec.fromList (Map.toList (view (uiVm . env . contracts) s)))- 2- , _browserVm = s- }+-- Vm Overview: C-n - step+appEvent (ViewVm s) (VtyEvent (V.EvKey (V.KChar 'n') [V.MCtrl])) =+ takeStep s+ (StepUntil (isNextSourcePositionWithoutEntering s)) - (UiVmScreen s, VtyEvent (Vty.EvKey (Vty.KChar ' ') [])) ->- let- loop = do- Just hey <- Readline.getInputLine "% "- Readline.outputStrLn hey- Just hey' <- Readline.getInputLine "% "- Readline.outputStrLn hey'- return (UiVmScreen s)- in- suspendAndResume $- Readline.runInputT Readline.defaultSettings loop+-- Vm Overview: e - step+appEvent (ViewVm s) (VtyEvent (V.EvKey (V.KChar 'e') [])) =+ takeStep s+ (StepUntil (isExecutionHalted s)) +-- Vm Overview: a - step+appEvent (ViewVm s) (VtyEvent (V.EvKey (V.KChar 'a') [])) =+ -- We keep the current cache so we don't have to redo+ -- any blocking queries.+ let+ (vm, stepper) = fromJust (Map.lookup 0 (view uiSnapshots s))+ s' = s+ & set uiVm vm+ & set (uiVm . cache) (view (uiVm . cache) s)+ & set uiStep 0+ & set uiStepper stepper - (UiVmScreen s, VtyEvent (Vty.EvKey (Vty.KChar 'n') [])) ->- takeStep s StepNormally StepOne+ in takeStep s' (Step 0) - (UiVmScreen s, VtyEvent (Vty.EvKey (Vty.KChar 'N') [])) ->- takeStep s- StepNormally- (StepUntil (isNextSourcePosition s))+-- Vm Overview: p - step+appEvent st@(ViewVm s) (VtyEvent (V.EvKey (V.KChar 'p') [])) =+ case view uiStep s of+ 0 ->+ -- We're already at the first step; ignore command.+ continue st+ n -> do+ -- To step backwards, we revert to the previous snapshot+ -- and execute n - 1 `mod` snapshotInterval steps from there.+ --+ -- We keep the current cache so we don't have to redo+ -- any blocking queries, and also the memory view.+ let+ (step, (vm, stepper)) = fromJust $ lookupLT n (view uiSnapshots s)+ s1 = s+ & set uiVm vm+ & set (uiVm . cache) (view (uiVm . cache) s)+ & set uiStep step+ & set uiStepper stepper+ stepsToTake = n - step - 1 - (UiVmScreen s, VtyEvent (Vty.EvKey (Vty.KChar 'n') [Vty.MCtrl])) ->- takeStep s- StepNormally- (StepUntil (isNextSourcePositionWithoutEntering s))+ takeStep s1 (Step stepsToTake) - (UiVmScreen s, VtyEvent (Vty.EvKey (Vty.KChar 'p') [])) ->- case view uiVmStepCount s of- 0 ->- -- We're already at the first step; ignore command.- continue ui+-- Vm Overview: 0 - choose no jump+appEvent (ViewVm s) (VtyEvent (V.EvKey (V.KChar '0') [])) =+ case view (uiVm . result) s of+ Just (VMFailure (Choose (PleaseChoosePath contin))) ->+ takeStep (s & set uiStepper (Stepper.evm (contin True) >> (view uiStepper s)))+ (Step 1)+ _ -> continue (ViewVm s) - n -> do- -- To step backwards, we revert to the first state- -- and execute n - 1 instructions from there.- --- -- We keep the current cache so we don't have to redo- -- any blocking queries, and also the memory view.- let- s0 = view uiVmFirstState s- s1 = set (uiVm . cache) (view (uiVm . cache) s) s0- s2 = set (uiVmShowMemory) (view uiVmShowMemory s) s1+-- Vm Overview: 1 - choose jump+appEvent (ViewVm s) (VtyEvent (V.EvKey (V.KChar '1') [])) =+ case view (uiVm . result) s of+ Just (VMFailure (Choose (PleaseChoosePath contin))) ->+ takeStep (s & set uiStepper (Stepper.evm (contin False) >> (view uiStepper s)))+ (Step 1)+ _ -> continue (ViewVm s) - -- Take n steps; "timidly," because all queries- -- ought to be cached.- takeStep s2 StepTimidly (StepMany (n - 1)) - (UiVmScreen s, VtyEvent (Vty.EvKey (Vty.KChar 'm') [])) ->- continue (UiVmScreen (over uiVmShowMemory not s))+-- Any: Esc - return to Vm Overview or Exit+appEvent s (VtyEvent (V.EvKey V.KEsc [])) =+ case s of+ (ViewHelp x) -> overview x+ (ViewContracts x) -> overview $ view browserVm x+ _ -> halt s+ where+ overview = continue . ViewVm - (UiTestPickerScreen s', VtyEvent (Vty.EvKey (Vty.KEnter) [])) -> do- case listSelectedElement (view testPickerList s') of- Nothing -> error "nothing selected"- Just (_, x) ->- continue . UiVmScreen $- initialUiVmStateForTest opts (view testPickerDapp s') x+-- UnitTest Picker: Enter - select from list+appEvent (ViewPicker s) (VtyEvent (V.EvKey V.KEnter [])) =+ case listSelectedElement (view testPickerList s) of+ Nothing -> error "nothing selected"+ Just (_, x) ->+ continue . ViewVm $+ initialUiVmStateForTest (view testOpts s) x+-- (view testPickerDapp s) x - (UiTestPickerScreen s', VtyEvent e') -> do- s'' <- handleEventLensed s'- testPickerList- handleListEvent- e'- continue (UiTestPickerScreen s'')+-- UnitTest Picker: (main) - render list+appEvent (ViewPicker s) (VtyEvent e) = do+ s' <- handleEventLensed s+ testPickerList+ handleListEvent+ e+ continue (ViewPicker s') - _ ->- continue ui+-- Page: Down - scroll+appEvent s (VtyEvent (V.EvKey V.KDown [])) =+ vScrollBy (viewportScroll TracePane) 1 >> continue s +-- Page: Up - scroll+appEvent s (VtyEvent (V.EvKey V.KUp [])) =+ vScrollBy (viewportScroll TracePane) (-1) >> continue s++-- Page: C-f - Page down+appEvent s (VtyEvent (V.EvKey (V.KChar 'f') [V.MCtrl])) =+ vScrollPage (viewportScroll TracePane) Down >> continue s++-- Page: C-b - Page up+appEvent s (VtyEvent (V.EvKey (V.KChar 'b') [V.MCtrl])) =+ vScrollPage (viewportScroll TracePane) Up >> continue s++-- Default+appEvent s _ = continue s++app :: UnitTestOptions -> App UiState () Name+app opts =+ let ?fetcher = oracle opts+ ?maxIter = maxIter opts+ in App+ { appDraw = drawUi+ , appChooseCursor = neverShowCursor+ , appHandleEvent = appEvent , appStartEvent = return- , appAttrMap = const (attrMap Vty.defAttr myTheme)+ , appAttrMap = const (attrMap V.defAttr myTheme) } initialUiVmStateForTest :: UnitTestOptions- -> DappInfo -> (Text, Text) -> UiVmState-initialUiVmStateForTest opts@(UnitTestOptions {..}) dapp (theContractName, theTestName) =- ui1+initialUiVmStateForTest opts@UnitTestOptions{..} (theContractName, theTestName) =+ ui where+ Just typesig = lookup theTestName (unitTestMethods testContract)+ args = case replay of+ Nothing -> emptyAbi+ Just (sig, callData) ->+ if theTestName == sig+ then decodeAbiValue (AbiTupleType (Vec.fromList typesig)) callData+ else emptyAbi script = do Stepper.evm . pushTrace . EntryTrace $ "test " <> theTestName <> " (" <> theContractName <> ")" initializeUnitTest opts- void (runUnitTest opts theTestName)- ui0 =- UiVmState- { _uiVm = vm0- , _uiVmNextStep = script- , _uiVmStackList = undefined- , _uiVmBytecodeList = undefined- , _uiVmTraceList = undefined- , _uiVmSolidityList = undefined- , _uiVmSolc = Just testContract- , _uiVmDapp = Just dapp- , _uiVmStepCount = 0- , _uiVmFirstState = undefined- , _uiVmMessage = Just "Creating unit test contract"- , _uiVmNotes = []- , _uiVmShowMemory = False- }+ void (runUnitTest opts theTestName args)+ ui = initUiVmState vm0 opts script Just testContract = view (dappSolcByName . at theContractName) dapp vm0 =- initialUnitTestVm opts testContract (Map.elems (view dappSolcByName dapp))- ui1 =- updateUiVmState ui0 vm0 & set uiVmFirstState ui1+ initialUnitTestVm opts testContract -myTheme :: [(AttrName, Vty.Attr)]+myTheme :: [(AttrName, V.Attr)] myTheme =- [ (selectedAttr, Vty.defAttr `Vty.withStyle` Vty.standout)- , (dimAttr, Vty.defAttr `Vty.withStyle` Vty.dim)- , (borderAttr, Vty.defAttr `Vty.withStyle` Vty.dim)- , (wordAttr, fg Vty.yellow)- , (boldAttr, Vty.defAttr `Vty.withStyle` Vty.bold)- , (activeAttr, Vty.defAttr `Vty.withStyle` Vty.standout)+ [ (selectedAttr, V.defAttr `V.withStyle` V.standout)+ , (dimAttr, V.defAttr `V.withStyle` V.dim)+ , (borderAttr, V.defAttr `V.withStyle` V.dim)+ , (wordAttr, fg V.yellow)+ , (boldAttr, V.defAttr `V.withStyle` V.bold)+ , (activeAttr, V.defAttr `V.withStyle` V.standout) ] drawUi :: UiState -> [UiWidget]-drawUi (UiVmScreen s) = drawVm s-drawUi (UiTestPickerScreen s) = drawTestPicker s-drawUi (UiVmBrowserScreen s) = drawVmBrowser s+drawUi (ViewVm s) = drawVm s+drawUi (ViewPicker s) = drawTestPicker s+drawUi (ViewContracts s) = drawVmBrowser s+drawUi (ViewHelp _) = drawHelpView +drawHelpView :: [UiWidget]+drawHelpView =+ [ center . borderWithLabel version .+ padLeftRight 4 . padTopBottom 2 . str $+ "Esc Exit the debugger\n\n" <>+ "a Step to start\n" <>+ "e Step to end\n" <>+ "n Step fwds by one instruction\n" <>+ "N Step fwds to the next source position\n" <>+ "C-n Step fwds to the next source position skipping CALL & CREATE\n" <>+ "p Step back by one instruction\n\n" <>+ "m Toggle memory pane\n" <>+ "0 Choose the branch which does not jump \n" <>+ "1 Choose the branch which does jump \n" <>+ "Down Scroll memory pane fwds\n" <>+ "Up Scroll memory pane back\n" <>+ "C-f Page memory pane fwds\n" <>+ "C-b Page memory pane back\n\n" <>+ "Enter Contracts browser"+ ]+ where+ version =+ txt "Hevm " <+>+ str (showVersion Paths.version) <+>+ txt " - Key bindings"+ drawTestPicker :: UiTestPickerState -> [UiWidget] drawTestPicker ui = [ center . borderWithLabel (txt "Unit tests") .@@ -520,21 +604,26 @@ [ borderWithLabel (txt "Contracts") . hLimit 60 $ renderList- (\selected (k, c) ->+ (\selected (k, c') -> withHighlight selected . txt . mconcat $- [ fromMaybe "<unknown contract>" . flip preview ui $- ( browserVm . uiVmDapp . _Just . dappSolcByHash . ix (view codehash c)+ [ fromMaybe "<unknown contract>" . flip preview dapp' $+ ( dappSolcByHash . ix (view codehash c') . _2 . contractName ) , "\n" , " ", pack (show k) ]) True (view browserContractList ui)- , let- Just (_, (_, c)) = listSelectedElement (view browserContractList ui)- Just dapp = view (browserVm . uiVmDapp) ui- in case flip preview ui (browserVm . uiVmDapp . _Just . dappSolcByHash . ix (view codehash c) . _2) of- Nothing -> txt ("n/a; codehash " <> pack (show (view codehash c)))+ , case flip preview dapp' (dappSolcByHash . ix (view codehash c) . _2) of+ Nothing ->+ hBox+ [ borderWithLabel (txt "Contract information") . padBottom Max . padRight Max $ vBox+ [ txt ("Codehash: " <> pack (show (view codehash c)))+ , txt ("Nonce: " <> showWordExact (view nonce c))+ , txt ("Balance: " <> showWordExact (view balance c))+ , txt ("Storage: " <> storageDisplay (view storage c))+ ]+ ] Just solc -> hBox [ borderWithLabel (txt "Contract information") . padBottom Max . padRight (Pad 2) $ vBox@@ -547,12 +636,18 @@ , txt "Public methods:" , vBox . flip map (sort (Map.elems (view abiMap solc))) $ \method -> txt (" " <> view methodSignature method)+ , txt ("Storage:" <> storageDisplay (view storage c)) ]- , borderWithLabel (txt "Storage slots") . padBottom Max . padRight Max $ vBox- (map txt (storageLayout dapp solc))- ]+ , borderWithLabel (txt "Storage slots") . padBottom Max . padRight Max $ vBox+ (map txt (storageLayout dapp' solc))+ ] ] ]+ where storageDisplay (Concrete s) = pack ( show ( Map.toList s))+ storageDisplay (Symbolic _) = pack "<symbolic>"+ dapp' = dapp (view (browserVm . uiTestOpts) ui)+ Just (_, (_, c)) = listSelectedElement (view browserContractList ui)+-- currentContract = view (dappSolcByHash . ix ) dapp drawVm :: UiVmState -> [UiWidget] drawVm ui =@@ -569,7 +664,7 @@ , vLimit 20 $ drawStackPane ui , drawSolidityPane ui , vLimit 20 $ drawTracePane ui- , vLimit 2 $ drawHelpBar+ , vLimit 2 drawHelpBar ] ) ( vBox@@ -577,11 +672,11 @@ [ vLimit 20 $ drawBytecodePane ui , vLimit 20 $ drawStackPane ui ]- , hBox $+ , hBox [ drawSolidityPane ui , drawTracePane ui ]- , vLimit 2 $ drawHelpBar+ , vLimit 2 drawHelpBar ] ) ]@@ -596,48 +691,48 @@ [ ("n", "step") , ("p", "step back")- , ("N", "step more")- , ("C-n", "step over")--- , (" Enter", "browse")+ , ("a", "step to start")+ , ("e", "step to end") , ("m", "toggle memory")- , (" Esc", "exit")+ , ("Esc", "exit")+ , ("h", "more help") ] -stepOneOpcode :: UiVmState -> UiVmState-stepOneOpcode ui =- let- nextVm = execState exec1 (view uiVm ui)- in- ui & over uiVmStepCount (+ 1)- & set uiVm nextVm+stepOneOpcode :: Stepper a -> StateT UiVmState IO ()+stepOneOpcode restart = do+ n <- use uiStep+ when (n > 0 && n `mod` snapshotInterval == 0) $ do+ vm <- use uiVm+ modifying uiSnapshots (insert n (vm, void restart))+ modifying uiVm (execState exec1)+ modifying uiStep (+ 1) + isNextSourcePosition :: UiVmState -> Pred VM isNextSourcePosition ui vm =- let- Just dapp = view uiVmDapp ui- initialPosition = currentSrcMap dapp (view uiVm ui)- in- currentSrcMap dapp vm /= initialPosition+ let dapp' = dapp (view uiTestOpts ui)+ initialPosition = currentSrcMap dapp' (view uiVm ui)+ in currentSrcMap dapp' vm /= initialPosition isNextSourcePositionWithoutEntering :: UiVmState -> Pred VM isNextSourcePositionWithoutEntering ui vm = let+ dapp' = dapp (view uiTestOpts ui) vm0 = view uiVm ui- Just dapp = view uiVmDapp ui- initialPosition = currentSrcMap dapp vm0+ initialPosition = currentSrcMap dapp' vm0 initialHeight = length (view frames vm0) in- case currentSrcMap dapp vm of+ case currentSrcMap dapp' vm of Nothing ->- True+ False Just here -> let moved = Just here /= initialPosition deeper = length (view frames vm) > initialHeight boring =- case srcMapCode (view dappSources dapp) here of+ case srcMapCode (view dappSources dapp') here of Just bs -> BS.isPrefixOf "contract " bs Nothing ->@@ -645,6 +740,9 @@ in moved && not deeper && not boring +isExecutionHalted :: UiVmState -> Pred VM+isExecutionHalted _ vm = isJust (view result vm)+ currentSrcMap :: DappInfo -> VM -> Maybe SrcMap currentSrcMap dapp vm = let@@ -674,44 +772,50 @@ updateUiVmState :: UiVmState -> VM -> UiVmState updateUiVmState ui vm = let- move = case vmOpIx vm of- Nothing -> id- Just x -> listMoveTo x- ui' = ui+ move = maybe id listMoveTo (vmOpIx vm)+ address = view (state . contract) vm+ message =+ case view result vm of+ Just (VMSuccess (ConcreteBuffer msg)) ->+ Just ("VMSuccess: " <> (show $ ByteStringS msg))+ Just (VMSuccess (SymbolicBuffer msg)) ->+ Just ("VMSuccess: <symbolicbuffer> " <> (show msg))+ Just (VMFailure (Revert msg)) ->+ Just ("VMFailure: " <> (show . ByteStringS $ msg))+ Just (VMFailure err) ->+ Just ("VMFailure: " <> show err)+ Nothing ->+ Just ("Executing EVM code in " <> show address)+ in ui & set uiVm vm- & set uiVmStackList+ & set uiStackList (list StackPane (Vec.fromList $ zip [1..] (view (state . stack) vm)) 2)- & set uiVmBytecodeList+ & set uiBytecodeList (move $ list BytecodePane (view codeOps (fromJust (currentContract vm))) 1)- in- case view uiVmDapp ui of- Nothing ->- ui'- & set uiVmTraceList (list TracePane mempty 1)- & set uiVmSolidityList (list SolidityPane mempty 1)- Just dapp ->- ui'- & set uiVmTraceList- (list- TracePane- (Vec.fromList- . Text.lines- . showTraceTree dapp- $ vm)- 1)- & set uiVmSolidityList- (list SolidityPane- (case currentSrcMap dapp vm of- Nothing -> mempty- Just x ->- view (dappSources- . sourceLines- . ix (srcMapFile x)- . to (Vec.imap (,)))- dapp)- 1)+ & set uiMessage message+ & set uiTraceList+ (list+ TracePane+ (Vec.fromList+ . Text.lines+ . showTraceTree dapp'+ $ vm)+ 1)+ & set uiSolidityList+ (list SolidityPane+ (case currentSrcMap dapp' vm of+ Nothing -> mempty+ Just x ->+ view (dappSources+ . sourceLines+ . ix (srcMapFile x)+ . to (Vec.imap (,)))+ dapp')+ 1)+ where+ dapp' = dapp (view uiTestOpts ui) drawStackPane :: UiVmState -> UiWidget drawStackPane ui =@@ -720,19 +824,20 @@ labelText = txt ("Gas available: " <> gasText <> "; stack:") in hBorderWithLabel labelText <=> renderList- (\_ (i, x@(C _ w)) ->+ (\_ (i, x@(S _ w)) -> vBox [ withHighlight True (str ("#" ++ show i ++ " ")) <+> str (show x)- , dim (txt (" " <> showWordExplanation w (view uiVmDapp ui)))+ , dim (txt (" " <> case unliteral w of+ Nothing -> ""+ Just u -> showWordExplanation (fromSizzle u) $ dapp (view uiTestOpts ui))) ]) False- (view uiVmStackList ui)+ (view uiStackList ui) -showWordExplanation :: W256 -> Maybe DappInfo -> Text-showWordExplanation w Nothing = showDec Unsigned w+showWordExplanation :: W256 -> DappInfo -> Text showWordExplanation w _ | w > 0xffffffff = showDec Unsigned w-showWordExplanation w (Just dapp) =+showWordExplanation w dapp = let fullAbiMap = mconcat (map (view abiMap) (Map.elems (view dappSolcByName dapp)))@@ -743,13 +848,13 @@ drawBytecodePane :: UiVmState -> UiWidget drawBytecodePane ui =- hBorderWithLabel (case view uiVmMessage ui of { Nothing -> str ""; Just s -> str s }) <=>+ hBorderWithLabel (case view uiMessage ui of { Nothing -> str ""; Just s -> str s }) <=> Centered.renderList (\active x -> if not active then withDefAttr dimAttr (opWidget x) else withDefAttr boldAttr (opWidget x)) False- (view uiVmBytecodeList ui)+ (view uiBytecodeList ui) dim :: Widget n -> Widget n dim = withDefAttr dimAttr@@ -758,49 +863,57 @@ withHighlight False = withDefAttr dimAttr withHighlight True = withDefAttr boldAttr +prettyIfConcrete :: Buffer -> String+prettyIfConcrete (SymbolicBuffer x) = show x+prettyIfConcrete (ConcreteBuffer x) = prettyHex 40 x+ drawTracePane :: UiVmState -> UiWidget-drawTracePane ui =- case view uiVmShowMemory ui of- False ->- hBorderWithLabel (txt "Trace") <=>- renderList- (\_ x -> txt x)- False- (view uiVmTraceList ui)+drawTracePane s =+ case view uiShowMemory s of True ->- vBox- [ hBorderWithLabel (txt "Calldata") <=>- str (prettyHex 40 (view (uiVm . state . calldata) ui))- , hBorderWithLabel (txt "Returndata") <=>- str (prettyHex 40 (view (uiVm . state . returndata) ui))- , hBorderWithLabel (txt "Memory") <=>- str (prettyHex 40 (view (uiVm . state . memory) ui))- ]+ hBorderWithLabel (txt "Calldata")+ <=> str (prettyIfConcrete $ fst (view (uiVm . state . calldata) s))+ <=> hBorderWithLabel (txt "Returndata")+ <=> str (prettyIfConcrete (view (uiVm . state . returndata) s))+ <=> hBorderWithLabel (txt "Output")+ <=> str (maybe "" show (view (uiVm . result) s))+ <=> hBorderWithLabel (txt "Cache")+ <=> str (show (view (uiVm . cache . path) s))+ <=> hBorderWithLabel (txt "Memory")+ <=> viewport TracePane Vertical+ (str (prettyIfConcrete (view (uiVm . state . memory) s)))+ False ->+ hBorderWithLabel (txt "Trace")+ <=> renderList+ (\_ x -> txt x)+ False+ (view uiTraceList s) drawSolidityPane :: UiVmState -> UiWidget-drawSolidityPane ui@(view uiVmDapp -> Just dapp) =- case currentSrcMap dapp (view uiVm ui) of+drawSolidityPane ui =+ let dapp' = dapp (view uiTestOpts ui)+ in case currentSrcMap dapp' (view uiVm ui) of Nothing -> padBottom Max (hBorderWithLabel (txt "<no source map>")) Just sm ->- case view (dappSources . sourceLines . at (srcMapFile sm)) dapp of+ case view (dappSources . sourceLines . at (srcMapFile sm)) dapp' of Nothing -> padBottom Max (hBorderWithLabel (txt "<source not found>")) Just rows -> let- subrange i = lineSubrange rows (srcMapOffset sm, srcMapLength sm) i+ subrange = lineSubrange rows (srcMapOffset sm, srcMapLength sm) lineNo = (snd . fromJust $ (srcMapCodePos- (view dappSources dapp)+ (view dappSources dapp') sm)) - 1 in vBox [ hBorderWithLabel $ txt (maybe "<unknown>" contractPathPart- (preview (uiVmSolc . _Just . contractName) ui))+ (preview (uiSolc . _Just . contractName) ui)) <+> str (":" ++ show lineNo) -- Show the AST node type if present <+> txt (" (" <> fromMaybe "?"- ((view dappAstSrcMap dapp) sm+ ((view dappAstSrcMap dapp') sm >>= preview (key "name" . _String)) <> ")") , Centered.renderList (\_ (i, line) ->@@ -818,12 +931,8 @@ ]) False (listMoveTo lineNo- (view uiVmSolidityList ui))+ (view uiSolidityList ui)) ]-drawSolidityPane _ =- -- When e.g. debugging raw EVM code without dapp info,- -- don't show a Solidity pane.- vBox [] ifTallEnough :: Int -> Widget n -> Widget n -> Widget n ifTallEnough need w1 w2 =@@ -833,84 +942,8 @@ then render w1 else render w2 -showPc :: (Integral a, Show a) => a -> String-showPc x =- if x < 0x10- then '0' : showHex x ""- else showHex x ""- opWidget :: (Integral a, Show a) => (a, Op) -> Widget n-opWidget (i, o) = str (showPc i <> " ") <+> case o of- OpStop -> txt "STOP"- OpAdd -> txt "ADD"- OpMul -> txt "MUL"- OpSub -> txt "SUB"- OpDiv -> txt "DIV"- OpSdiv -> txt "SDIV"- OpMod -> txt "MOD"- OpSmod -> txt "SMOD"- OpAddmod -> txt "ADDMOD"- OpMulmod -> txt "MULMOD"- OpExp -> txt "EXP"- OpSignextend -> txt "SIGNEXTEND"- OpLt -> txt "LT"- OpGt -> txt "GT"- OpSlt -> txt "SLT"- OpSgt -> txt "SGT"- OpEq -> txt "EQ"- OpIszero -> txt "ISZERO"- OpAnd -> txt "AND"- OpOr -> txt "OR"- OpXor -> txt "XOR"- OpNot -> txt "NOT"- OpByte -> txt "BYTE"- OpSha3 -> txt "SHA3"- OpAddress -> txt "ADDRESS"- OpBalance -> txt "BALANCE"- OpOrigin -> txt "ORIGIN"- OpCaller -> txt "CALLER"- OpCallvalue -> txt "CALLVALUE"- OpCalldataload -> txt "CALLDATALOAD"- OpCalldatasize -> txt "CALLDATASIZE"- OpCalldatacopy -> txt "CALLDATACOPY"- OpCodesize -> txt "CODESIZE"- OpCodecopy -> txt "CODECOPY"- OpGasprice -> txt "GASPRICE"- OpExtcodesize -> txt "EXTCODESIZE"- OpExtcodecopy -> txt "EXTCODECOPY"- OpReturndatasize -> txt "RETURNDATASIZE"- OpReturndatacopy -> txt "RETURNDATACOPY"- OpBlockhash -> txt "BLOCKHASH"- OpCoinbase -> txt "COINBASE"- OpTimestamp -> txt "TIMESTAMP"- OpNumber -> txt "NUMBER"- OpDifficulty -> txt "DIFFICULTY"- OpGaslimit -> txt "GASLIMIT"- OpPop -> txt "POP"- OpMload -> txt "MLOAD"- OpMstore -> txt "MSTORE"- OpMstore8 -> txt "MSTORE8"- OpSload -> txt "SLOAD"- OpSstore -> txt "SSTORE"- OpJump -> txt "JUMP"- OpJumpi -> txt "JUMPI"- OpPc -> txt "PC"- OpMsize -> txt "MSIZE"- OpGas -> txt "GAS"- OpJumpdest -> txt "JUMPDEST"- OpCreate -> txt "CREATE"- OpCall -> txt "CALL"- OpStaticcall -> txt "STATICCALL"- OpCallcode -> txt "CALLCODE"- OpReturn -> txt "RETURN"- OpDelegatecall -> txt "DELEGATECALL"- OpSelfdestruct -> txt "SELFDESTRUCT"- OpDup x -> txt "DUP" <+> str (show x)- OpSwap x -> txt "SWAP" <+> str (show x)- OpLog x -> txt "LOG" <+> str (show x)- OpPush x -> txt "PUSH " <+> withDefAttr wordAttr (str (show x))- OpRevert -> txt "REVERT"- OpUnknown x -> txt "UNKNOWN " <+> str (show x)+opWidget = txt . pack . opString selectedAttr :: AttrName; selectedAttr = "selected" dimAttr :: AttrName; dimAttr = "dim"
@@ -0,0 +1,92 @@+module EVM.Transaction where++import Prelude hiding (Word)++import EVM.Concrete+import EVM.FeeSchedule+import EVM.Keccak (keccak)+import EVM.Precompiled (execute)+import EVM.RLP+import EVM.Types++import Data.Aeson (FromJSON (..))+import Data.ByteString (ByteString)+import Data.Maybe (isNothing)++import qualified Data.Aeson as JSON+import qualified Data.Aeson.Types as JSON+import qualified Data.ByteString as BS++data Transaction = Transaction+ { txData :: ByteString,+ txGasLimit :: W256,+ txGasPrice :: W256,+ txNonce :: W256,+ txR :: W256,+ txS :: W256,+ txToAddr :: Maybe Addr,+ txV :: W256,+ txValue :: W256+ } deriving Show++ecrec :: W256 -> W256 -> W256 -> W256 -> Maybe Addr+ecrec e v r s = (num . word) <$> EVM.Precompiled.execute 1 input 32+ where input = BS.concat (word256Bytes <$> [e, v, r, s])++sender :: Int -> Transaction -> Maybe Addr+sender chainId tx = ecrec hash v' (txR tx) (txS tx)+ where hash = keccak $ signingData chainId tx+ v = txV tx+ v' = if v == 27 || v == 28 then v+ else 28 - mod v 2++signingData :: Int -> Transaction -> ByteString+signingData chainId tx =+ if v == (chainId * 2 + 35) || v == (chainId * 2 + 36)+ then eip155Data+ else normalData+ where v = fromIntegral (txV tx)+ to' = case txToAddr tx of+ Just a -> BS $ word160Bytes a+ Nothing -> BS mempty+ normalData = rlpList [rlpWord256 (txNonce tx),+ rlpWord256 (txGasPrice tx),+ rlpWord256 (txGasLimit tx),+ to',+ rlpWord256 (txValue tx),+ BS (txData tx)]+ eip155Data = rlpList [rlpWord256 (txNonce tx),+ rlpWord256 (txGasPrice tx),+ rlpWord256 (txGasLimit tx),+ to',+ rlpWord256 (txValue tx),+ BS (txData tx),+ rlpWord256 (fromIntegral chainId),+ rlpWord256 0x0,+ rlpWord256 0x0]++txGasCost :: FeeSchedule Word -> Transaction -> Word+txGasCost fs tx =+ let calldata = txData tx+ zeroBytes = BS.count 0 calldata+ nonZeroBytes = BS.length calldata - zeroBytes+ baseCost = g_transaction fs+ + if isNothing (txToAddr tx) then g_txcreate fs else 0+ zeroCost = g_txdatazero fs+ nonZeroCost = g_txdatanonzero fs+ in baseCost + zeroCost * (fromIntegral zeroBytes) + nonZeroCost * (fromIntegral nonZeroBytes)++instance FromJSON Transaction where+ parseJSON (JSON.Object val) = do+ tdata <- dataField val "data"+ gasLimit <- wordField val "gasLimit"+ gasPrice <- wordField val "gasPrice"+ nonce <- wordField val "nonce"+ r <- wordField val "r"+ s <- wordField val "s"+ toAddr <- addrFieldMaybe val "to"+ v <- wordField val "v"+ value <- wordField val "value"+ return $ Transaction tdata gasLimit gasPrice nonce r s toAddr v value+ parseJSON invalid =+ JSON.typeMismatch "Transaction" invalid
@@ -1,21 +1,31 @@ {-# Language CPP #-} {-# Language TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE FlexibleInstances #-} module EVM.Types where -import Data.Aeson ((.:))-import Data.Aeson (FromJSON (..))+import Data.Aeson (FromJSON (..), (.:)) #if MIN_VERSION_aeson(1, 0, 0) import Data.Aeson (FromJSONKey (..), FromJSONKeyFunction (..)) #endif +import Data.SBV+import Data.Kind import Data.Monoid ((<>))-import Data.Bits+import Data.Bifunctor (first)+import Data.Char import Data.ByteString (ByteString) import Data.ByteString.Base16 as BS16+import Data.ByteString.Builder (byteStringHex, toLazyByteString)+import Data.ByteString.Lazy (toStrict)+import qualified Data.ByteString.Char8 as Char8 import Data.DoubleWord import Data.DoubleWord.TH+import Data.Maybe (fromMaybe) import Data.Word (Word8) import Numeric (readHex, showHex) import Options.Generic@@ -26,6 +36,7 @@ import qualified Data.Serialize.Get as Cereal import qualified Data.Text as Text import qualified Data.Text.Encoding as Text+import qualified Text.Read -- Some stuff for "generic programming", needed to create Word512 import Data.Data@@ -40,12 +51,48 @@ , Bits, FiniteBits, Bounded, Generic ) +-- | convert between (WordN 256) and Word256+type family ToSizzle (t :: Type) :: Type where+ ToSizzle W256 = (WordN 256)+ ToSizzle Addr = (WordN 160)++-- | Conversion from a fixed-sized BV to a sized bit-vector.+class ToSizzleBV a where+ -- | Convert a fixed-sized bit-vector to the corresponding sized bit-vector,+ toSizzle :: a -> ToSizzle a++ default toSizzle :: (Num (ToSizzle a), Integral a) => (a -> ToSizzle a)+ toSizzle = fromIntegral++-- | Capture the correspondence between sized and fixed-sized BVs+type family FromSizzle (t :: Type) :: Type where+ FromSizzle (WordN 256) = W256+ FromSizzle (WordN 160) = Addr+++-- | Conversion from a sized BV to a fixed-sized bit-vector.+class FromSizzleBV a where+ -- | Convert a sized bit-vector to the corresponding fixed-sized bit-vector,+ -- for instance 'SWord 16' to 'SWord16'. See also 'toSized'.+ fromSizzle :: a -> FromSizzle a++ default fromSizzle :: (Num (FromSizzle a), Integral a) => a -> FromSizzle a+ fromSizzle = fromIntegral+ +instance (ToSizzleBV W256)+instance (FromSizzleBV (WordN 256))+instance (ToSizzleBV Addr)+instance (FromSizzleBV (WordN 160))+ newtype Addr = Addr { addressWord160 :: Word160 } deriving (Num, Integral, Real, Ord, Enum, Eq, Bits, Generic) +newtype SAddr = SAddr { saddressWord160 :: SWord 160 }+ deriving (Num)+ instance Read W256 where readsPrec _ "0x" = [(0, "")]- readsPrec n s = (\(x, r) -> (W256 x, r)) <$> readsPrec n s+ readsPrec n s = first W256 <$> readsPrec n s instance Show W256 where showsPrec _ s = ("0x" ++) . showHex s@@ -57,17 +104,29 @@ instance Show Addr where showsPrec _ s a = let h = showHex s a- in replicate (40 - length h) '0' ++ h+ in "0x" ++ replicate (40 - length h) '0' ++ h -showAddrWith0x :: Addr -> String-showAddrWith0x addr = "0x" ++ show addr+instance Show SAddr where+ show (SAddr a) = case unliteral a of+ Nothing -> "<symbolic addr>"+ Just c -> show c -showWordWith0x :: W256 -> String-showWordWith0x addr = show addr+strip0x :: ByteString -> ByteString+strip0x bs = if "0x" `Char8.isPrefixOf` bs then Char8.drop 2 bs else bs -showByteStringWith0x :: ByteString -> String-showByteStringWith0x bs = Text.unpack (Text.decodeUtf8 (BS16.encode bs))+newtype ByteStringS = ByteStringS ByteString deriving (Eq) +instance Show ByteStringS where+ show (ByteStringS x) = ("0x" ++) . Text.unpack . fromBinary $ x+ where+ fromBinary =+ Text.decodeUtf8 . toStrict . toLazyByteString . byteStringHex++instance Read ByteStringS where+ readsPrec _ ('0':'x':x) = [(ByteStringS $ fst bytes, Text.unpack . Text.decodeUtf8 $ snd bytes)]+ where bytes = BS16.decode (Text.encodeUtf8 (Text.pack x))+ readsPrec _ _ = []+ instance FromJSON W256 where parseJSON v = do s <- Text.unpack <$> parseJSON v@@ -123,13 +182,19 @@ readN :: Integral a => String -> a readN s = fromIntegral (read s :: Integer) +readNull :: Read a => a -> String -> a+readNull x = fromMaybe x . Text.Read.readMaybe+ wordField :: JSON.Object -> Text -> JSON.Parser W256-wordField x f = (read . Text.unpack)+wordField x f = ((readNull 0) . Text.unpack) <$> (x .: f) addrField :: JSON.Object -> Text -> JSON.Parser Addr addrField x f = (read . Text.unpack) <$> (x .: f) +addrFieldMaybe :: JSON.Object -> Text -> JSON.Parser (Maybe Addr)+addrFieldMaybe x f = (Text.Read.readMaybe . Text.unpack) <$> (x .: f)+ dataField :: JSON.Object -> Text -> JSON.Parser ByteString dataField x f = hexText <$> (x .: f) @@ -149,10 +214,15 @@ padRight :: Int -> ByteString -> ByteString padRight n xs = xs <> BS.replicate (n - BS.length xs) 0 -word :: ByteString -> W256-word xs = case Cereal.runGet m (padLeft 32 xs) of- Left _ -> error "internal error"- Right x -> W256 x+truncpad :: Int -> [SWord 8] -> [SWord 8]+truncpad n xs = if m > n then take n xs+ else mappend xs (replicate (n - m) 0)+ where m = length xs++word256 :: ByteString -> Word256+word256 xs = case Cereal.runGet m (padLeft 32 xs) of+ Left _ -> error "internal error"+ Right x -> x where m = do a <- Cereal.getWord64be b <- Cereal.getWord64be@@ -160,5 +230,49 @@ d <- Cereal.getWord64be return $ fromHiAndLo (fromHiAndLo a b) (fromHiAndLo c d) +word :: ByteString -> W256+word = W256 . word256+ byteAt :: (Bits a, Bits b, Integral a, Num b) => a -> Int -> b byteAt x j = num (x `shiftR` (j * 8)) .&. 0xff++fromBE :: (Integral a) => ByteString -> a+fromBE xs = if xs == mempty then 0+ else 256 * fromBE (BS.init xs)+ + (num $ BS.last xs)++asBE :: (Integral a) => a -> ByteString+asBE 0 = mempty+asBE x = asBE (x `div` 256)+ <> BS.pack [num $ x `mod` 256]++word256Bytes :: W256 -> ByteString+word256Bytes x = BS.pack [byteAt x (31 - i) | i <- [0..31]]++word160Bytes :: Addr -> ByteString+word160Bytes x = BS.pack [byteAt (addressWord160 x) (19 - i) | i <- [0..19]]++newtype Nibble = Nibble Word8+ deriving ( Num, Integral, Real, Ord, Enum, Eq+ , Bits, FiniteBits, Bounded, Generic)++instance Show Nibble where+ show = (:[]) . intToDigit . num++--Get first and second Nibble from byte+hi, lo :: Word8 -> Nibble+hi b = Nibble $ b `shiftR` 4+lo b = Nibble $ b .&. 0x0f++toByte :: Nibble -> Nibble -> Word8+toByte (Nibble high) (Nibble low) = high `shift` 4 .|. low++unpackNibbles :: ByteString -> [Nibble]+unpackNibbles bs = BS.unpack bs >>= unpackByte+ where unpackByte b = [hi b, lo b]++--Well-defined for even length lists only (plz dependent types)+packNibbles :: [Nibble] -> ByteString+packNibbles [] = mempty+packNibbles (n1:n2:ns) = BS.singleton (toByte n1 n2) <> packNibbles ns+packNibbles _ = error "cant pack odd number of nibbles"
@@ -1,41 +1,40 @@-{-# LANGUAGE ViewPatterns #-}- module EVM.UnitTest where import Prelude hiding (Word) import EVM import EVM.ABI+import EVM.Concrete hiding (readMemoryWord)+import EVM.Symbolic import EVM.Dapp import EVM.Debug (srcMapCodePos) import EVM.Exec import EVM.Format-import EVM.Keccak import EVM.Solidity import EVM.Types-import EVM.Concrete (w256, wordAt) import qualified EVM.FeeSchedule as FeeSchedule -import EVM.Stepper (Stepper)+import EVM.Stepper (Stepper, interpret) import qualified EVM.Stepper as Stepper import qualified Control.Monad.Operational as Operational import Control.Lens hiding (Indexed)+import Control.Monad ((>=>)) import Control.Monad.State.Strict hiding (state) import qualified Control.Monad.State.Strict as State import Control.Monad.Par.Class (spawn_) import Control.Monad.Par.IO (runParIO) +import qualified Data.ByteString.Lazy as BSLazy import Data.ByteString (ByteString)+import Data.SBV import Data.Foldable (toList) import Data.Map (Map)-import Data.Maybe (fromMaybe, catMaybes, fromJust, fromMaybe, mapMaybe)+import Data.Maybe (fromMaybe, catMaybes, fromJust, isJust, fromMaybe, mapMaybe) import Data.Monoid ((<>))-import Data.Text (Text, pack, unpack)-import Data.Text (isPrefixOf, stripSuffix, intercalate)-import Data.Text.Encoding (encodeUtf8)+import Data.Text (isPrefixOf, stripSuffix, intercalate, Text, pack, unpack) import Data.Word (Word32) import System.Environment (lookupEnv) import System.IO (hFlush, stdout)@@ -56,12 +55,17 @@ import Data.Vector (Vector) import qualified Data.Vector as Vector +import Test.QuickCheck hiding (verbose)+ data UnitTestOptions = UnitTestOptions- {- oracle :: Query -> IO (EVM ())- , verbose :: Bool+ { oracle :: Query -> IO (EVM ())+ , verbose :: Maybe Int+ , maxIter :: Maybe Integer , match :: Text+ , fuzzRuns :: Int+ , replay :: Maybe (Text, BSLazy.ByteString) , vmModifier :: VM -> VM+ , dapp :: DappInfo , testParams :: TestVMParams } @@ -78,7 +82,9 @@ , testTimestamp :: W256 , testGaslimit :: W256 , testGasprice :: W256+ , testMaxCodeSize :: W256 , testDifficulty :: W256+ , testChainId :: W256 } defaultGasForCreating :: W256@@ -93,6 +99,9 @@ defaultBalanceForCreated :: W256 defaultBalanceForCreated = 0xffffffffffffffffffffffff +defaultMaxCodeSize :: W256+defaultMaxCodeSize = 0xffffffff+ type ABIMethod = Text -- | Assuming a constructor is loaded, this stepper will run the constructor@@ -100,117 +109,79 @@ initializeUnitTest :: UnitTestOptions -> Stepper () initializeUnitTest UnitTestOptions { .. } = do - -- Maybe modify the initial VM, e.g. to load library code- Stepper.evm (modify vmModifier)+ let addr = testAddress testParams - -- Make a trace entry for running the constructor- Stepper.evm (pushTrace (EntryTrace "constructor"))+ Stepper.evm $ do+ -- Maybe modify the initial VM, e.g. to load library code+ modify vmModifier+ -- Make a trace entry for running the constructor+ pushTrace (EntryTrace "constructor") -- Constructor is loaded; run until it returns code- bytes <- Stepper.execFullyOrFail- addr <- Stepper.evm (use (state . contract))-- -- Mutate the current contract to use the new code- Stepper.evm $ replaceCodeOfSelf bytes-- -- Increase the nonce, in case the constructor created contracts- Just n <- Stepper.evm (preuse (env . contracts . ix ethrunAddress . nonce))- Stepper.evm $ assign (env . contracts . ix addr . nonce) n+ void Stepper.execFully -- Give a balance to the test target- Stepper.evm $+ Stepper.evm $ do env . contracts . ix addr . balance += w256 (testBalanceCreate testParams) - -- Initialize the test contract- Stepper.evm (popTrace >> pushTrace (EntryTrace "initialize test"))- Stepper.evm $- setupCall addr "setUp()" (testBalanceCall testParams)-- Stepper.note "Running `setUp()'"+ -- Initialize the test contract+ setupCall testParams "setUp()" emptyAbi+ popTrace+ pushTrace (EntryTrace "initialize test") -- Let `setUp()' run to completion- void Stepper.execFullyOrFail- Stepper.evm popTrace+ res <- Stepper.execFully+ Stepper.evm $ case res of+ Left e -> pushTrace (ErrorTrace e)+ _ -> popTrace + -- | Assuming a test contract is loaded and initialized, this stepper -- will run the specified test method and return whether it succeeded.-runUnitTest :: UnitTestOptions -> ABIMethod -> Stepper Bool-runUnitTest UnitTestOptions { .. } method = do- -- Fail immediately if there was a failure in the setUp() phase- Stepper.evm (use result) >>=- \case- Just (VMFailure e) -> do- Stepper.evm (pushTrace (ErrorTrace e))- pure False-- _ -> do- -- Decide whether the test is supposed to fail or succeed- let shouldFail = "testFail" `isPrefixOf` method-- -- The test subject should be loaded and initialized already- addr <- Stepper.evm $ use (state . contract)-- -- Set up the call to the test method- Stepper.evm $- setupCall addr method (testGasCall testParams)- Stepper.evm (pushTrace (EntryTrace method))- Stepper.note "Running unit test"-- -- Try running the test method- bailed <-- Stepper.execFully >>=- either (const (pure True)) (const (pure False))+runUnitTest :: UnitTestOptions -> ABIMethod -> AbiValue -> Stepper Bool+runUnitTest a method args = do+ x <- execTest a method args+ checkFailures a method args x - -- If we failed, put the error in the trace.- -- It's not clear to me right now why this doesn't happen somewhere else.- Just problem <- Stepper.evm $ use result- case problem of- VMFailure e ->- Stepper.evm (pushTrace (ErrorTrace e))- _ ->- pure ()+execTest :: UnitTestOptions -> ABIMethod -> AbiValue -> Stepper Bool+execTest UnitTestOptions { .. } method args = do+ -- Set up the call to the test method+ Stepper.evm $ do+ setupCall testParams method args+ pushTrace (EntryTrace method)+ -- Try running the test method+ Stepper.execFully >>= \case+ -- If we failed, put the error in the trace.+ Left e -> Stepper.evm (pushTrace (ErrorTrace e)) >> pure True+ _ -> pure False - -- Ask whether any assertions failed- Stepper.evm $ popTrace- Stepper.evm $ setupCall addr "failed()" 10000- Stepper.note "Checking whether assertions failed"- AbiBool failed <- Stepper.execFullyOrFail >>= Stepper.decode AbiBoolType+checkFailures :: UnitTestOptions -> ABIMethod -> AbiValue -> Bool -> Stepper Bool+checkFailures UnitTestOptions { .. } method args bailed = do+ -- Decide whether the test is supposed to fail or succeed+ let shouldFail = "testFail" `isPrefixOf` method+ if bailed then+ pure shouldFail+ else do+ -- Ask whether any assertions failed+ Stepper.evm $ do+ popTrace+ setupCall testParams "failed()" args+ res <- Stepper.execFully -- >>= \(ConcreteBuffer bs) -> (Stepper.decode AbiBoolType bs)+ case res of+ Right (ConcreteBuffer r) ->+ let AbiBool failed = decodeAbiValue AbiBoolType (BSLazy.fromStrict r)+ in pure (shouldFail == failed)+ _ -> error "internal error: unexpected failure code" - -- Return true if the test was successful- pure (shouldFail == (bailed || failed))+-- | Randomly generates the calldata arguments and runs the test+fuzzTest :: UnitTestOptions -> Text -> [AbiType] -> VM -> Property+fuzzTest opts sig types vm = forAllShow (genAbiValue (AbiTupleType $ Vector.fromList types)) (show . ByteStringS . encodeAbiValue)+ $ \args -> ioProperty $+ fst <$> runStateT (interpret (oracle opts) (runUnitTest opts sig args)) vm tick :: Text -> IO () tick x = Text.putStr x >> hFlush stdout -interpret- :: UnitTestOptions- -> Stepper a- -> StateT VM IO (Either Stepper.Failure a)-interpret opts =- eval . Operational.view-- where- eval- :: Operational.ProgramView Stepper.Action a- -> StateT VM IO (Either Stepper.Failure a)-- eval (Operational.Return x) =- pure (Right x)-- eval (action Operational.:>>= k) =- case action of- Stepper.Exec ->- exec >>= interpret opts . k- Stepper.Wait q ->- do m <- liftIO (oracle opts q)- State.state (runState m) >> interpret opts (k ())- Stepper.Note _ ->- interpret opts (k ())- Stepper.Fail e ->- pure (Left e)- Stepper.EVM m ->- State.state (runState m) >>= interpret opts . k- -- | This is like an unresolved source mapping. data OpLocation = OpLocation { srcCodehash :: !W256@@ -243,7 +214,11 @@ (fromMaybe (error "internal error: op ix") (vmOpIx vm)) execWithCoverage :: StateT CoverageState IO VMResult-execWithCoverage = do+execWithCoverage = do _ <- runWithCoverage+ fromJust <$> use (_1 . result)++runWithCoverage :: StateT CoverageState IO VM+runWithCoverage = do -- This is just like `exec` except for every instruction evaluated, -- we also increment a counter indexed by the current code location. vm0 <- use _1@@ -251,36 +226,36 @@ Nothing -> do vm1 <- zoom _1 (State.state (runState exec1) >> get) zoom _2 (modify (MultiSet.insert (currentOpLocation vm1)))- execWithCoverage- Just r ->- pure r+ runWithCoverage+ Just _ -> pure vm0 + interpretWithCoverage :: UnitTestOptions -> Stepper a- -> StateT CoverageState IO (Either Stepper.Failure a)+ -> StateT CoverageState IO a interpretWithCoverage opts = eval . Operational.view where eval :: Operational.ProgramView Stepper.Action a- -> StateT CoverageState IO (Either Stepper.Failure a)+ -> StateT CoverageState IO a eval (Operational.Return x) =- pure (Right x)+ pure x eval (action Operational.:>>= k) = case action of Stepper.Exec -> execWithCoverage >>= interpretWithCoverage opts . k+ Stepper.Run ->+ runWithCoverage >>= interpretWithCoverage opts . k Stepper.Wait q -> do m <- liftIO (oracle opts q) zoom _1 (State.state (runState m)) >> interpretWithCoverage opts (k ())- Stepper.Note _ ->- interpretWithCoverage opts (k ())- Stepper.Fail e ->- pure (Left e)+ Stepper.Ask _ ->+ error "cannot make choice in this interpreter" Stepper.EVM m -> zoom _1 (State.state (runState m)) >>= interpretWithCoverage opts . k @@ -319,7 +294,7 @@ ) f :: Text -> Vector ByteString -> Vector (Int, ByteString)- f name xs =+ f name = Vector.imap (\i bs -> let@@ -328,7 +303,6 @@ then MultiSet.occur (name, i + 1) srcMapCov else -1 in (n, bs))- xs in Map.mapWithKey f linesByName @@ -336,7 +310,7 @@ :: UnitTestOptions -> Map Text SolcContract -> SourceCache- -> (Text, [Text])+ -> (Text, [(Text, [AbiType])]) -> IO (MultiSet SrcMap) coverageForUnitTestContract opts@(UnitTestOptions {..}) contractMap sources (name, testNames) = do@@ -349,7 +323,7 @@ Just theContract -> do -- Construct the initial VM and begin the contract's constructor- let vm0 = initialUnitTestVm opts theContract (Map.elems contractMap)+ let vm0 = initialUnitTestVm opts theContract (vm1, cov1) <- execStateT (interpretWithCoverage opts@@ -358,15 +332,12 @@ -- Define the thread spawner for test cases let- runOne testName = spawn_ . liftIO $ do+ runOne (testName, _) = spawn_ . liftIO $ do (x, (_, cov)) <- runStateT- (interpretWithCoverage opts (runUnitTest opts testName))+ (interpretWithCoverage opts (runUnitTest opts testName emptyAbi)) (vm1, mempty)- case x of- Right True -> pure cov- _ -> error "test failure during coverage analysis; fix it!"-+ pure cov -- Run all the test cases in parallel and gather their coverages covs <- runParIO (mapM runOne testNames >>= mapM Par.get)@@ -374,22 +345,19 @@ -- Sum up all the coverage counts let cov2 = MultiSet.unions (cov1 : covs) - -- Gather the dapp-related metadata- let dapp = dappInfo "." contractMap sources- pure (MultiSet.mapMaybe (srcMapForOpLocation dapp) cov2) runUnitTestContract :: UnitTestOptions -> Map Text SolcContract -> SourceCache- -> (Text, [Text])+ -> (Text, [(Text, [AbiType])]) -> IO Bool runUnitTestContract- opts@(UnitTestOptions {..}) contractMap sources (name, testNames) = do+ opts@(UnitTestOptions {..}) contractMap sources (name, testSigs) = do -- Print a header- putStrLn $ "Running " ++ show (length testNames) ++ " tests for "+ putStrLn $ "Running " ++ show (length testSigs) ++ " tests for " ++ unpack name -- Look for the wanted contract by name from the Solidity info@@ -400,66 +368,113 @@ Just theContract -> do -- Construct the initial VM and begin the contract's constructor- let vm0 = initialUnitTestVm opts theContract (Map.elems contractMap)+ let vm0 = initialUnitTestVm opts theContract vm1 <- execStateT- (interpret opts+ (interpret oracle (Stepper.enter name >> initializeUnitTest opts)) vm0 - -- Gather the dapp-related metadata- let dapp = dappInfo "." contractMap sources+ case view result vm1 of+ Nothing -> error "internal error: setUp() did not end with a result"+ Just (VMFailure _) -> do+ Text.putStrLn "\x1b[31m[FAIL]\x1b[0m setUp()"+ tick "\n"+ tick $ failOutput vm1 opts "setUp()"+ pure False+ Just (VMSuccess _) -> do - -- Define the thread spawner for test cases- let- runOne testName = do- x <-- runStateT- (interpret opts (runUnitTest opts testName))- vm1- case x of- (Right True, vm) ->- let- gasSpent =- view burned vm - view burned vm1- gasText =- pack . show $- (fromIntegral gasSpent :: Integer)- in- pure- ( "PASS " <> testName <> " (gas: " <> gasText <> ")"- , Right (passOutput vm dapp opts testName)- )- (Right False, vm) ->- pure ("FAIL " <> testName, Left (failOutput vm dapp opts testName))- (Left _, _) ->- pure ("OOPS " <> testName, Left ("VM error for " <> testName))+ -- Define the thread spawner for normal test cases+ let+ runOne testName args = do+ let argInfo = pack (if args == emptyAbi then "" else " with arguments: " <> show args)+ (bailed, vm2) <-+ runStateT+ (interpret oracle (execTest opts testName args))+ vm1+ (success, vm) <- runStateT (interpret oracle (checkFailures opts testName args bailed)) vm2+ if success+ then+ let gasSpent = num (testGasCall testParams) - view (state . gas) vm2+ gasText = pack . show $ (fromIntegral gasSpent :: Integer)+ in+ pure+ ("\x1b[32m[PASS]\x1b[0m "+ <> testName <> argInfo <> " (gas: " <> gasText <> ")"+ , Right (passOutput vm opts testName)+ )+ else+ pure+ ("\x1b[31m[FAIL]\x1b[0m "+ <> testName <> argInfo+ , Left (failOutput vm opts testName)+ ) - let inform = \(x, y) -> Text.putStrLn x >> pure y+ -- Define the thread spawner for property based tests+ let fuzzRun (testName, types) = do+ let args = Args{ replay = Nothing+ , maxSuccess = fuzzRuns+ , maxDiscardRatio = 10+ , maxSize = 100+ , chatty = isJust verbose+ , maxShrinks = maxBound+ }+ res <- quickCheckWithResult args (fuzzTest opts testName types vm1)+ case res of+ Success numTests _ _ _ _ _ ->+ pure ("\x1b[32m[PASS]\x1b[0m "+ <> testName <> " (runs: " <> (pack $ show numTests) <> ")",+ -- vm1 isn't quite the post vm we want...+ -- but the vm we want is not accessible here anyway...+ Right (passOutput vm1 opts testName))+ Failure _ _ _ _ _ _ _ _ _ _ failCase _ _ ->+ let abiValue = decodeAbiValue (AbiTupleType (Vector.fromList types)) $ BSLazy.fromStrict $ hexText (pack $ concat failCase)+ ppOutput = pack $ show abiValue+ in do+ -- Run the failing test again to get a proper trace+ vm2 <- execStateT (interpret oracle (runUnitTest opts testName abiValue)) vm1+ pure ("\x1b[31m[FAIL]\x1b[0m "+ <> testName <> ". Counterexample: " <> ppOutput+ <> "\nRun:\n dapp test --replay '(\"" <> testName <> "\",\""+ <> (pack (concat failCase)) <> "\")'\nto test this case again, or \n dapp debug --replay '(\""+ <> testName <> "\",\"" <> (pack (concat failCase)) <> "\")'\nto debug it.",+ Left (failOutput vm2 opts testName))+ _ -> pure ("\x1b[31m[OOPS]\x1b[0m "+ <> testName, Left (failOutput vm1 opts testName)) - -- Run all the test cases and print their status updates- details <-- mapM (\x -> runOne x >>= inform) testNames+ let runTest (testName, []) = runOne testName emptyAbi+ runTest (testName, types) = case replay of+ Nothing -> fuzzRun (testName, types)+ Just (sig, callData) -> if sig == testName+ then runOne testName $+ decodeAbiValue (AbiTupleType (Vector.fromList types)) callData+ else fuzzRun (testName, types) - let mails = [x | Right x <- details]- let fails = [x | Left x <- details]+ let inform (x, y) = Text.putStrLn x >> pure y - tick "\n"- tick (Text.unlines (filter (not . Text.null) mails))- tick (Text.unlines (filter (not . Text.null) fails))+ -- Run all the test cases and print their status updates+ details <-+ mapM (runTest >=> inform) testSigs - pure (null fails)+ let running = [x | Right x <- details]+ let bailing = [x | Left x <- details] + tick "\n"+ tick (Text.unlines (filter (not . Text.null) running))+ tick (Text.unlines (filter (not . Text.null) bailing))++ pure (null bailing)+ indentLines :: Int -> Text -> Text indentLines n s = let p = Text.replicate n " " in Text.unlines (map (p <>) (Text.lines s)) -passOutput :: VM -> DappInfo -> UnitTestOptions -> Text -> Text-passOutput vm dapp UnitTestOptions { .. } testName =+passOutput :: VM -> UnitTestOptions -> Text -> Text+passOutput vm UnitTestOptions { .. } testName = case verbose of- True ->- mconcat $+ Just 2 ->+ mconcat [ "Success: " , fromMaybe "" (stripSuffix "()" testName) , "\n"@@ -467,17 +482,19 @@ , indentLines 2 (showTraceTree dapp vm) , "\n" ]- False ->+ _ -> "" -failOutput :: VM -> DappInfo -> UnitTestOptions -> Text -> Text-failOutput vm dapp _ testName = mconcat $+failOutput :: VM -> UnitTestOptions -> Text -> Text+failOutput vm UnitTestOptions { .. } testName = mconcat [ "Failure: " , fromMaybe "" (stripSuffix "()" testName) , "\n" , indentLines 2 (formatTestLogs (view dappEventMap dapp) (view logs vm))- , indentLines 2 (showTraceTree dapp vm)- , "\n"+ , case verbose of+ Just _ -> indentLines 2 (showTraceTree dapp vm)+ _ -> ""+ ] formatTestLogs :: Map W256 Event -> Seq.Seq Log -> Text@@ -488,75 +505,85 @@ formatTestLog :: Map W256 Event -> Log -> Maybe Text formatTestLog _ (Log _ _ []) = Nothing-formatTestLog events (Log _ args (t:_)) =- let- name = getEventName event- event = getEvent t events-- in case name of-- "log_bytes32" ->- Just $ formatBytes args+formatTestLog events (Log _ args (topic:_)) =+ case maybeLitWord topic of+ Nothing -> Nothing+ Just t -> case (Map.lookup (wordValue t) events) of+ Nothing -> Nothing+ Just (Event name _ _) -> case name of+ "log_bytes32" ->+ Just $ formatSBytes args - "log_named_bytes32" ->- let key = BS.take 32 args- val = BS.drop 32 args- in Just $ formatString key <> ": " <> formatBytes val+ "log_named_bytes32" ->+ let key = grab 32 args+ val = ditch 32 args+ in Just $ formatSString key <> ": " <> formatSBytes val - "log_named_address" ->- let key = BS.take 32 args- val = BS.drop 44 args- in Just $ formatString key <> ": " <> formatBinary val+ "log_named_address" ->+ let key = grab 32 args+ val = ditch 44 args+ in Just $ formatSString key <> ": " <> formatSBinary val - -- TODO: event logs (bytes);- -- TODO: event log_named_decimal_int (bytes32 key, int val, uint decimals);- -- TODO: event log_named_decimal_uint (bytes32 key, uint val, uint decimals);+ "log_named_int" ->+ let key = grab 32 args+ val = case maybeLitWord (readMemoryWord 32 args) of+ Just c -> showDec Signed (wordValue c)+ Nothing -> "<symbolic int>"+ in Just $ formatSString key <> ": " <> val - "log_named_int" ->- let key = BS.take 32 args- val = wordAt 32 args- in Just $ formatString key <> ": " <> showDec Signed val+ "log_named_uint" ->+ let key = grab 32 args+ val = case maybeLitWord (readMemoryWord 32 args) of+ Just c -> showDec Unsigned (wordValue c)+ Nothing -> "<symbolic uint>"+ in Just $ formatSString key <> ": " <> val - "log_named_uint" ->- let key = BS.take 32 args- val = wordAt 32 args- in Just $ formatString key <> ": " <> showDec Unsigned val+-- TODO: event logs (bytes);+-- TODO: event log_named_decimal_int (bytes32 key, int val, uint decimals);+-- TODO: event log_named_decimal_uint (bytes32 key, uint val, uint decimals); - _ ->- Nothing+ _ -> Nothing word32Bytes :: Word32 -> ByteString word32Bytes x = BS.pack [byteAt x (3 - i) | i <- [0..3]] -setupCall :: Addr -> Text -> W256 -> EVM ()-setupCall target abi allowance = do+setupCall :: TestVMParams -> Text -> AbiValue -> EVM ()+setupCall TestVMParams{..} sig args = do resetState- loadContract target- assign (state . calldata) (word32Bytes (abiKeccak (encodeUtf8 abi)))- assign (state . gas) (w256 allowance)+ use (env . contracts) >>= assign (tx . txReversion)+ assign (tx . isCreate) False+ loadContract testAddress+ assign (state . calldata) $ (ConcreteBuffer $ abiMethod sig args, literal . num . BS.length $ abiMethod sig args)+ assign (state . caller) (litAddr testCaller)+ assign (state . gas) (w256 testGasCall) -initialUnitTestVm :: UnitTestOptions -> SolcContract -> [SolcContract] -> VM-initialUnitTestVm (UnitTestOptions {..}) theContract _ =+initialUnitTestVm :: UnitTestOptions -> SolcContract -> VM+initialUnitTestVm (UnitTestOptions {..}) theContract = let TestVMParams {..} = testParams vm = makeVm $ VMOpts- { vmoptCode = view creationCode theContract- , vmoptCalldata = ""+ { vmoptContract = initialContract (InitCode (view creationCode theContract))+ , vmoptCalldata = (mempty, 0) , vmoptValue = 0 , vmoptAddress = testAddress- , vmoptCaller = testCaller+ , vmoptCaller = litAddr testCaller , vmoptOrigin = testOrigin , vmoptGas = testGasCreate+ , vmoptGaslimit = testGasCreate , vmoptCoinbase = testCoinbase , vmoptNumber = testNumber , vmoptTimestamp = testTimestamp- , vmoptGaslimit = testGaslimit+ , vmoptBlockGaslimit = testGaslimit , vmoptGasprice = testGasprice+ , vmoptMaxCodeSize = testMaxCodeSize , vmoptDifficulty = testDifficulty- , vmoptSchedule = FeeSchedule.metropolis+ , vmoptSchedule = FeeSchedule.istanbul+ , vmoptChainId = testChainId+ , vmoptCreate = True+ , vmoptStorageModel = ConcreteS } creator =- initialContract mempty+ initialContract (RuntimeCode mempty) & set nonce 1 & set balance (w256 testBalanceCreate) in vm@@ -569,7 +596,7 @@ getAddr s def = maybe def read <$> lookupEnv s TestVMParams- <$> getAddr "DAPP_TEST_ADDRESS" (newContractAddress ethrunAddress 1)+ <$> getAddr "DAPP_TEST_ADDRESS" (createAddress ethrunAddress 1) <*> getAddr "DAPP_TEST_CALLER" ethrunAddress <*> getAddr "DAPP_TEST_ORIGIN" ethrunAddress <*> getWord "DAPP_TEST_GAS_CREATE" defaultGasForCreating@@ -577,8 +604,10 @@ <*> getWord "DAPP_TEST_BALANCE_CREATE" defaultBalanceForCreator <*> getWord "DAPP_TEST_BALANCE_CALL" defaultBalanceForCreated <*> getAddr "DAPP_TEST_COINBASE" 0- <*> getWord "DAPP_TEST_NUMBER" 512+ <*> getWord "DAPP_TEST_NUMBER" 0 <*> getWord "DAPP_TEST_TIMESTAMP" 1 <*> getWord "DAPP_TEST_GAS_LIMIT" 0 <*> getWord "DAPP_TEST_GAS_PRICE" 0+ <*> getWord "DAPP_TEST_MAXCODESIZE" defaultMaxCodeSize <*> getWord "DAPP_TEST_DIFFICULTY" 1+ <*> getWord "DAPP_TEST_CHAINID" 99
@@ -5,121 +5,248 @@ ( Case #if MIN_VERSION_aeson(1, 0, 0) , parseSuite+ , parseBCSuite #endif , vmForCase , checkExpectation- , interpret ) where import qualified EVM import qualified EVM.Concrete as EVM-import qualified EVM.Exec-import qualified EVM.FeeSchedule as EVM.FeeSchedule-import qualified EVM.Stepper as Stepper-import qualified EVM.Fetch as Fetch--import Control.Monad.State.Strict (runState, join)-import qualified Control.Monad.Operational as Operational-import qualified Control.Monad.State.Class as State+import qualified EVM.FeeSchedule -import EVM (EVM)-import EVM.Stepper (Stepper)+import EVM.Symbolic+import EVM.Transaction import EVM.Types -import Control.Lens+import Data.SBV -import IPPrint.Colored (cpprint)+import Control.Arrow ((***), (&&&))+import Control.Lens+import Control.Monad import Data.ByteString (ByteString)-import Data.Aeson ((.:), (.:?))-import Data.Aeson (FromJSON (..))+import Data.Aeson ((.:), (.:?), FromJSON (..))+import Data.Bifunctor (bimap)+import Data.Foldable (fold) import Data.Map (Map)-import Data.List (intercalate)+import Data.Maybe (fromMaybe)+import Data.Witherable (Filterable, catMaybes) import qualified Data.Map as Map import qualified Data.Aeson as JSON import qualified Data.Aeson.Types as JSON import qualified Data.ByteString.Lazy as Lazy+import qualified Data.ByteString as BS +data Which = Pre | Post++data Block = Block+ { blockCoinbase :: Addr+ , blockDifficulty :: W256+ , blockGasLimit :: W256+ , blockNumber :: W256+ , blockTimestamp :: W256+ , blockTxs :: [Transaction]+ } deriving Show+ data Case = Case { testVmOpts :: EVM.VMOpts- , testContracts :: Map Addr Contract+ , checkContracts :: Map Addr Contract , testExpectation :: Maybe Expectation } deriving Show +data BlockchainCase = BlockchainCase+ { blockchainBlocks :: [Block]+ , blockchainPre :: Map Addr Contract+ , blockchainPost :: Map Addr Contract+ , blockchainNetwork :: String+ } deriving Show+ data Contract = Contract- { contractBalance :: W256- , contractCode :: ByteString- , contractNonce :: W256- , contractStorage :: Map W256 W256+ { _balance :: W256+ , _code :: EVM.ContractCode+ , _nonce :: W256+ , _storage :: Map W256 W256+ , _create :: Bool } deriving Show data Expectation = Expectation- { expectedOut :: ByteString+ { expectedOut :: Maybe ByteString , expectedContracts :: Map Addr Contract- , expectedGas :: W256+ , expectedGas :: Maybe W256 } deriving Show -checkExpectation :: Case -> EVM.VM -> IO Bool-checkExpectation x vm =+makeLenses ''Contract++accountAt :: Addr -> Getter (Map Addr Contract) Contract+accountAt a = (at a) . (to $ fromMaybe newAccount)++touchAccount :: Addr -> Map Addr Contract -> Map Addr Contract+touchAccount a = Map.insertWith (flip const) a newAccount++newAccount :: Contract+newAccount = Contract+ { _balance = 0+ , _code = EVM.RuntimeCode mempty+ , _nonce = 0+ , _storage = mempty+ , _create = False+ }++splitEithers :: (Filterable f) => f (Either a b) -> (f a, f b)+splitEithers =+ (catMaybes *** catMaybes)+ . (fmap fst &&& fmap snd)+ . (fmap (preview _Left &&& preview _Right))++checkStateFail :: Bool -> Case -> Expectation -> EVM.VM -> (Bool, Bool, Bool, Bool, Bool) -> IO Bool+checkStateFail diff x expectation vm (okState, okMoney, okNonce, okData, okCode) = do+ let+ printField :: (v -> String) -> Map Addr v -> IO ()+ printField f d = putStrLn $ Map.foldrWithKey (\k v acc ->+ acc ++ show k ++ " : " ++ f v ++ "\n") "" d++ reason = map fst (filter (not . snd)+ [ ("bad-state", okMoney || okNonce || okData || okCode || okState)+ , ("bad-balance", not okMoney || okNonce || okData || okCode || okState)+ , ("bad-nonce", not okNonce || okMoney || okData || okCode || okState)+ , ("bad-storage", not okData || okMoney || okNonce || okCode || okState)+ , ("bad-code", not okCode || okMoney || okNonce || okData || okState)+ ])+ check = checkContracts x+ initial = initTx x+ expected = expectedContracts expectation+ actual = view (EVM.env . EVM.contracts . to (fmap (clearZeroStorage.clearOrigStorage))) vm+ printStorage (EVM.Symbolic c) = show c+ printStorage (EVM.Concrete c) = show $ Map.toList c++ putStr (unwords reason)+ when (diff && (not okState)) $ do+ putStrLn "\nCheck balance/state: "+ printField (\v -> (show . toInteger $ _nonce v) ++ " "+ ++ (show . toInteger $ _balance v) ++ " "+ ++ (show . Map.toList $ _storage v)) check+ putStrLn "\nInitial balance/state: "+ printField (\v -> (show . toInteger $ _nonce v) ++ " "+ ++ (show . toInteger $ _balance v) ++ " "+ ++ (show . Map.toList $ _storage v)) initial+ putStrLn "\nExpected balance/state: "+ printField (\v -> (show . toInteger $ _nonce v) ++ " "+ ++ (show . toInteger $ _balance v) ++ " "+ ++ (show . Map.toList $ _storage v)) expected+ putStrLn "\nActual balance/state: "+ printField (\v -> (show . toInteger $ EVM._nonce v) ++ " "+ ++ (show . toInteger $ EVM._balance v) ++ " "+ ++ (printStorage $ EVM._storage v)) actual+ return okState++checkExpectation :: Bool -> Case -> EVM.VM -> IO Bool+checkExpectation diff x vm = case (testExpectation x, view EVM.result vm) of- (Just expectation, Just (EVM.VMSuccess output)) -> do- let- (s1, b1) = ("bad-state", checkExpectedContracts vm (expectedContracts expectation))- (s2, b2) = ("bad-output", checkExpectedOut output (expectedOut expectation))- (s3, b3) = ("bad-gas", checkExpectedGas vm (expectedGas expectation))- ss = map fst (filter (not . snd) [(s1, b1), (s2, b2), (s3, b3)])- putStr (intercalate " " ss)- return (b1 && b2 && b3)+ (Just expectation, _) -> do+ let (okState, b2, b3, b4, b5) =+ checkExpectedContracts vm (expectedContracts expectation)+ _ <- if not okState then+ checkStateFail+ diff x expectation vm (okState, b2, b3, b4, b5)+ else return True+ return okState+ (Nothing, Just (EVM.VMSuccess _)) -> do putStr "unexpected-success" return False+ (Nothing, Just (EVM.VMFailure _)) -> return True- (Just _, Just (EVM.VMFailure _)) -> do- putStr "unexpected-failure"- return False+ (_, Nothing) -> do- cpprint (view EVM.result vm)+ print (view EVM.result vm) error "internal error" -checkExpectedOut :: ByteString -> ByteString -> Bool-checkExpectedOut output expected =- output == expected+-- quotient account state by nullness+(~=) :: Map Addr EVM.Contract -> Map Addr EVM.Contract -> Bool+(~=) cs cs' =+ let nullAccount = EVM.initialContract (EVM.RuntimeCode mempty)+ padNewAccounts cs'' ks = (fold [Map.insertWith (\_ x -> x) k nullAccount | k <- ks]) cs''+ padded_cs' = padNewAccounts cs' (Map.keys cs)+ padded_cs = padNewAccounts cs (Map.keys cs')+ in padded_cs == padded_cs' -checkExpectedContracts :: EVM.VM -> Map Addr Contract -> Bool+checkExpectedContracts :: EVM.VM -> Map Addr Contract -> (Bool, Bool, Bool, Bool, Bool) checkExpectedContracts vm expected =- realizeContracts expected == vm ^. EVM.env . EVM.contracts . to (fmap clearZeroStorage)+ let cs = vm ^. EVM.env . EVM.contracts . to (fmap (clearZeroStorage.clearOrigStorage))+ expectedCs = clearOrigStorage <$> realizeContracts expected+ in ( (expectedCs ~= cs)+ , (clearBalance <$> expectedCs) ~= (clearBalance <$> cs)+ , (clearNonce <$> expectedCs) ~= (clearNonce <$> cs)+ , (clearStorage <$> expectedCs) ~= (clearStorage <$> cs)+ , (clearCode <$> expectedCs) ~= (clearCode <$> cs)+ ) +clearOrigStorage :: EVM.Contract -> EVM.Contract+clearOrigStorage = set EVM.origStorage mempty+ clearZeroStorage :: EVM.Contract -> EVM.Contract-clearZeroStorage =- over EVM.storage (Map.filterWithKey (\_ x -> x /= 0))+clearZeroStorage c = case EVM._storage c of+ EVM.Symbolic _ -> c+ EVM.Concrete m -> let store = Map.filter (\x -> forceLit x /= 0) m+ in set EVM.storage (EVM.Concrete store) c -checkExpectedGas :: EVM.VM -> W256 -> Bool-checkExpectedGas vm expected =- case vm ^. EVM.state . EVM.gas of- EVM.C _ x | x == expected -> True- _ -> False+clearStorage :: EVM.Contract -> EVM.Contract+clearStorage = set EVM.storage (EVM.Concrete mempty) +clearBalance :: EVM.Contract -> EVM.Contract+clearBalance = set EVM.balance 0++clearNonce :: EVM.Contract -> EVM.Contract+clearNonce = set EVM.nonce 0++clearCode :: EVM.Contract -> EVM.Contract+clearCode = set EVM.contractcode (EVM.RuntimeCode mempty)+ #if MIN_VERSION_aeson(1, 0, 0) instance FromJSON Contract where parseJSON (JSON.Object v) = Contract <$> v .: "balance"- <*> (hexText <$> v .: "code")+ <*> (EVM.RuntimeCode <$> (hexText <$> v .: "code")) <*> v .: "nonce" <*> v .: "storage"+ <*> pure False parseJSON invalid = JSON.typeMismatch "VM test case contract" invalid instance FromJSON Case where parseJSON (JSON.Object v) = Case <$> parseVmOpts v- <*> parseContracts v+ <*> parseContracts Pre v <*> parseExpectation v parseJSON invalid =- JSON.typeMismatch "VM test case" invalid+ JSON.typeMismatch "VM test case" invalid +instance FromJSON BlockchainCase where+ parseJSON (JSON.Object v) = BlockchainCase+ <$> v .: "blocks"+ <*> parseContracts Pre v+ <*> parseContracts Post v+ <*> v .: "network"+ parseJSON invalid =+ JSON.typeMismatch "GeneralState test case" invalid++instance FromJSON Block where+ parseJSON (JSON.Object v) = do+ v' <- v .: "blockHeader"+ txs <- v .: "transactions"+ coinbase <- addrField v' "coinbase"+ difficulty <- wordField v' "difficulty"+ gasLimit <- wordField v' "gasLimit"+ number <- wordField v' "number"+ timestamp <- wordField v' "timestamp"+ return $ Block coinbase difficulty gasLimit number timestamp txs+ parseJSON invalid =+ JSON.typeMismatch "Block" invalid+ parseVmOpts :: JSON.Object -> JSON.Parser EVM.VMOpts parseVmOpts v = do envV <- v .: "env"@@ -127,27 +254,35 @@ case (envV, execV) of (JSON.Object env, JSON.Object exec) -> EVM.VMOpts- <$> dataField exec "code"- <*> dataField exec "data"- <*> wordField exec "value"+ <$> (dataField exec "code" >>= pure . EVM.initialContract . EVM.RuntimeCode)+ <*> (dataField exec "data" >>= \a -> pure ( (ConcreteBuffer a), literal . num $ BS.length a))+ <*> (w256lit <$> wordField exec "value") <*> addrField exec "address"- <*> addrField exec "caller"+ <*> (litAddr <$> addrField exec "caller") <*> addrField exec "origin" <*> wordField exec "gas" -- XXX: correct?+ <*> wordField exec "gas" -- XXX: correct? <*> wordField env "currentNumber" <*> wordField env "currentTimestamp" <*> addrField env "currentCoinbase" <*> wordField env "currentDifficulty"+ <*> pure 0xffffffff <*> wordField env "currentGasLimit" <*> wordField exec "gasPrice"- <*> pure (EVM.FeeSchedule.homestead)+ <*> pure (EVM.FeeSchedule.istanbul)+ <*> pure 1+ <*> pure False+ <*> pure EVM.ConcreteS _ -> JSON.typeMismatch "VM test case" (JSON.Object v) parseContracts ::- JSON.Object -> JSON.Parser (Map Addr Contract)-parseContracts v =- v .: "pre" >>= parseJSON+ Which -> JSON.Object -> JSON.Parser (Map Addr Contract)+parseContracts w v =+ v .: which >>= parseJSON+ where which = case w of+ Pre -> "pre"+ Post -> "postState" parseExpectation :: JSON.Object -> JSON.Parser (Maybe Expectation) parseExpectation v =@@ -156,7 +291,7 @@ gas <- v .:? "gas" case (out, contracts, gas) of (Just x, Just y, Just z) ->- return (Just (Expectation x y z))+ return (Just (Expectation (Just x) y (Just z))) _ -> return Nothing @@ -164,6 +299,20 @@ Lazy.ByteString -> Either String (Map String Case) parseSuite = JSON.eitherDecode' +parseBCSuite ::+ Lazy.ByteString -> Either String (Map String Case)+parseBCSuite x = case (JSON.eitherDecode' x) :: Either String (Map String BlockchainCase) of+ Left e -> Left e+ Right bcCases -> let allCases = (fromBlockchainCase <$> bcCases)+ keepError (Left e) = errorFatal e+ keepError _ = True+ filteredCases = Map.filter keepError allCases+ (erroredCases, parsedCases) = splitEithers filteredCases+ in if Map.size erroredCases > 0+ then Left ("errored case: " ++ (show $ (Map.elems erroredCases) !! 0))+ else if Map.size parsedCases == 0+ then Left "No cases to check."+ else Right parsedCases #endif realizeContracts :: Map Addr Contract -> Map Addr EVM.Contract@@ -173,43 +322,205 @@ realizeContract :: Contract -> EVM.Contract realizeContract x =- EVM.initialContract (contractCode x)- & EVM.balance .~ EVM.w256 (contractBalance x)- & EVM.nonce .~ EVM.w256 (contractNonce x)- & EVM.storage .~ (+ EVM.initialContract (x ^. code)+ & EVM.balance .~ EVM.w256 (x ^. balance)+ & EVM.nonce .~ EVM.w256 (x ^. nonce)+ & EVM.storage .~ EVM.Concrete ( Map.fromList .- map (\(k, v) -> (EVM.w256 k, EVM.w256 v)) .- Map.toList $ contractStorage x+ map (bimap EVM.w256 (litWord . EVM.w256)) .+ Map.toList $ x ^. storage )+ & EVM.origStorage .~ (+ Map.fromList .+ map (bimap EVM.w256 EVM.w256) .+ Map.toList $ x ^. storage+ ) -vmForCase :: Case -> EVM.VM-vmForCase x =- EVM.makeVm (testVmOpts x)- & EVM.env . EVM.contracts .~ realizeContracts (testContracts x)- & EVM.execMode .~ EVM.ExecuteAsVMTest+data BlockchainError+ = TooManyBlocks+ | TooManyTxs+ | NoTxs+ | TargetMissing+ | SignatureUnverified+ | InvalidTx+ | OldNetwork+ | FailedCreate+ deriving Show -interpret :: Stepper a -> EVM a-interpret =- eval . Operational.view+errorFatal :: BlockchainError -> Bool+errorFatal TooManyBlocks = True+errorFatal TooManyTxs = True+errorFatal TargetMissing = True+errorFatal SignatureUnverified = True+errorFatal InvalidTx = True+errorFatal _ = False - where- eval- :: Operational.ProgramView Stepper.Action a- -> EVM a+fromBlockchainCase :: BlockchainCase -> Either BlockchainError Case+fromBlockchainCase (BlockchainCase blocks preState postState network) =+ case (blocks, network) of+ ([block], "Istanbul") -> case blockTxs block of+ [tx] -> case txToAddr tx of+ Nothing -> fromCreateBlockchainCase block tx preState postState+ Just _ -> fromNormalBlockchainCase block tx preState postState+ [] -> Left NoTxs+ _ -> Left TooManyTxs+ ([_], _) -> Left OldNetwork+ (_, _) -> Left TooManyBlocks - eval (Operational.Return x) =- pure x+fromCreateBlockchainCase :: Block -> Transaction+ -> Map Addr Contract -> Map Addr Contract+ -> Either BlockchainError Case+fromCreateBlockchainCase block tx preState postState =+ case (sender 1 tx,+ checkCreateTx tx block preState) of+ (Nothing, _) -> Left SignatureUnverified+ (_, Nothing) -> Left FailedCreate+ (Just origin, Just (checkState, createdAddr)) -> let+ feeSchedule = EVM.FeeSchedule.istanbul+ in Right $ Case+ (EVM.VMOpts+ { vmoptContract = EVM.initialContract (EVM.InitCode (txData tx))+ , vmoptCalldata = (mempty, 0)+ , vmoptValue = w256lit $ txValue tx+ , vmoptAddress = createdAddr+ , vmoptCaller = (litAddr origin)+ , vmoptOrigin = origin+ , vmoptGas = txGasLimit tx - fromIntegral (txGasCost feeSchedule tx)+ , vmoptGaslimit = txGasLimit tx+ , vmoptNumber = blockNumber block+ , vmoptTimestamp = blockTimestamp block+ , vmoptCoinbase = blockCoinbase block+ , vmoptDifficulty = blockDifficulty block+ , vmoptMaxCodeSize = 24576+ , vmoptBlockGaslimit = blockGasLimit block+ , vmoptGasprice = txGasPrice tx+ , vmoptSchedule = feeSchedule+ , vmoptChainId = 1+ , vmoptCreate = True+ , vmoptStorageModel = EVM.ConcreteS+ })+ checkState+ (Just $ Expectation Nothing postState Nothing) - eval (action Operational.:>>= k) =- case action of- Stepper.Exec ->- EVM.Exec.exec >>= interpret . k- Stepper.Wait q ->- do join (Fetch.zero q)- interpret (k ())- Stepper.Note _ ->- interpret (k ())- Stepper.Fail _ ->- error "VMTest stepper not supposed to fail"- Stepper.EVM m ->- State.state (runState m) >>= interpret . k++fromNormalBlockchainCase :: Block -> Transaction+ -> Map Addr Contract -> Map Addr Contract+ -> Either BlockchainError Case+fromNormalBlockchainCase block tx preState postState =+ let Just toAddr = txToAddr tx+ feeSchedule = EVM.FeeSchedule.istanbul+ toCode = Map.lookup toAddr preState+ theCode = case toCode of+ Nothing -> EVM.RuntimeCode mempty+ Just c -> view code c+ in case (toAddr , toCode , sender 1 tx , checkNormalTx tx block preState) of+ (_, _, Nothing, _) -> Left SignatureUnverified+ (_, _, _, Nothing) -> Left InvalidTx+ (_, _, Just origin, Just checkState) -> Right $ Case+ (EVM.VMOpts+ { vmoptContract = EVM.initialContract theCode+ , vmoptCalldata = (ConcreteBuffer $ txData tx, literal . num . BS.length $ txData tx)+ , vmoptValue = litWord (EVM.w256 $ txValue tx)+ , vmoptAddress = toAddr+ , vmoptCaller = (litAddr origin)+ , vmoptOrigin = origin+ , vmoptGas = txGasLimit tx - fromIntegral (txGasCost feeSchedule tx)+ , vmoptGaslimit = txGasLimit tx+ , vmoptNumber = blockNumber block+ , vmoptTimestamp = blockTimestamp block+ , vmoptCoinbase = blockCoinbase block+ , vmoptDifficulty = blockDifficulty block+ , vmoptMaxCodeSize = 24576+ , vmoptBlockGaslimit = blockGasLimit block+ , vmoptGasprice = txGasPrice tx+ , vmoptSchedule = feeSchedule+ , vmoptChainId = 1+ , vmoptCreate = False+ , vmoptStorageModel = EVM.ConcreteS+ })+ checkState+ (Just $ Expectation Nothing postState Nothing)+++validateTx :: Transaction -> Map Addr Contract -> Maybe Bool+validateTx tx cs = do+ origin <- sender 1 tx+ originBalance <- (view balance) <$> view (at origin) cs+ originNonce <- (view nonce) <$> view (at origin) cs+ let gasDeposit = fromIntegral (txGasPrice tx) * (txGasLimit tx)+ return $ gasDeposit + (txValue tx) <= originBalance+ && (txNonce tx) == originNonce++checkNormalTx :: Transaction -> Block -> Map Addr Contract -> Maybe (Map Addr Contract)+checkNormalTx tx block prestate = do+ toAddr <- txToAddr tx+ origin <- sender 1 tx+ valid <- validateTx tx prestate+ let gasDeposit = fromIntegral (txGasPrice tx) * (txGasLimit tx)+ coinbase = blockCoinbase block+ if not valid then mzero else+ return $+ (Map.adjust ((over nonce (+ 1))+ . (over balance (subtract gasDeposit))) origin)+ . touchAccount origin+ . touchAccount toAddr+ . touchAccount coinbase $ prestate++checkCreateTx :: Transaction -> Block -> Map Addr Contract -> Maybe ((Map Addr Contract), Addr)+checkCreateTx tx block prestate = do+ origin <- sender 1 tx+ valid <- validateTx tx prestate+ let gasDeposit = fromIntegral (txGasPrice tx) * (txGasLimit tx)+ coinbase = blockCoinbase block+ senderNonce = view (accountAt origin . nonce) prestate+ createdAddr = EVM.createAddress origin senderNonce+ prevCode = view (accountAt createdAddr . code) prestate+ prevNonce = view (accountAt createdAddr . nonce) prestate+ if (prevCode /= EVM.RuntimeCode mempty) || (prevNonce /= 0) || (not valid)+ then mzero+ else+ return+ ((Map.adjust ((over nonce (+ 1))+ . (over balance (subtract gasDeposit))) origin)+ . touchAccount origin+ . touchAccount createdAddr+ . touchAccount coinbase $ prestate, createdAddr)++initTx :: Case -> Map Addr Contract+initTx x =+ let+ checkState = checkContracts x+ opts = testVmOpts x+ toAddr = EVM.vmoptAddress opts+ origin = EVM.vmoptOrigin opts+ value = EVM.vmoptValue opts+ initcode = EVM._contractcode (EVM.vmoptContract opts)+ creation = EVM.vmoptCreate opts+ in+ (Map.adjust (over balance (subtract (EVM.wordValue $ forceLit value))) origin)+ . (Map.adjust (over balance (+ (EVM.wordValue $ forceLit value))) toAddr)+ . (if creation+ then (Map.adjust (set code initcode) toAddr)+ . (Map.adjust (set nonce 1) toAddr)+ . (Map.adjust (set storage mempty) toAddr)+ . (Map.adjust (set create True) toAddr)+ else id)+ $ checkState++vmForCase :: Case -> EVM.VM+vmForCase x =+ let+ checkState = checkContracts x+ initState = initTx x+ opts = testVmOpts x+ creation = EVM.vmoptCreate opts+ touchedAccounts =+ if creation then+ [EVM.vmoptOrigin opts]+ else+ [EVM.vmoptOrigin opts, EVM.vmoptAddress opts]+ in+ EVM.makeVm (testVmOpts x)+ & EVM.env . EVM.contracts .~ realizeContracts initState+ & EVM.tx . EVM.txReversion .~ realizeContracts checkState+ & EVM.tx . EVM.substate . EVM.touchedAccounts .~ touchedAccounts
@@ -1,33 +1,51 @@ {-# Language OverloadedStrings #-} {-# Language QuasiQuotes #-}-+{-# Language TypeSynonymInstances #-}+{-# Language FlexibleInstances #-}+{-# Language GeneralizedNewtypeDeriving #-}+{-# Language DataKinds #-}+{-# Language StandaloneDeriving #-} import Data.Text (Text) import Data.ByteString (ByteString) +import Prelude hiding (fail)+ import qualified Data.Text as Text import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BS (fromStrict) import qualified Data.ByteString.Base16 as Hex- import Test.Tasty-import Test.Tasty.QuickCheck (testProperty, Arbitrary (..), NonNegative (..))+import Test.Tasty.QuickCheck-- hiding (forAll) import Test.Tasty.HUnit import Control.Monad.State.Strict (execState, runState)-import Control.Lens+import Control.Lens hiding (List, pre) import qualified Data.Vector as Vector import Data.String.Here +import Control.Monad.Fail+ import Data.Binary.Put (runPut)+import Data.SBV hiding ((===), forAll)+import Data.SBV.Control+import qualified Data.Map as Map import Data.Binary.Get (runGetOrFail) -import EVM+import EVM hiding (Query)+import EVM.SymExec+import EVM.Symbolic import EVM.ABI import EVM.Exec+import EVM.Patricia as Patricia+import EVM.Precompiled+import EVM.RLP import EVM.Solidity import EVM.Types-import EVM.Precompiled +instance MonadFail Query where+ fail = io . fail+ main :: IO () main = defaultMain $ testGroup "hevm" [ testGroup "ABI"@@ -36,9 +54,8 @@ Right ("", _, x') -> x' == x _ -> False ]- , testGroup "Solidity expressions"- [ testCase "Trivial" $ do+ [ testCase "Trivial" $ SolidityCall "x = 3;" [] ===> AbiUInt 256 3 @@ -48,32 +65,12 @@ SolidityCall "x = a - 1;" [AbiUInt 8 0] ===> AbiUInt 8 255 - , testCase "keccak256()" $ do+ , testCase "keccak256()" $ SolidityCall "x = uint(keccak256(abi.encodePacked(a)));" [AbiString ""] ===> AbiUInt 256 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 ] - , testGroup "ecrecover"- [ testCase "Example 1" $- let- h = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"- r = "0xc84e55cee2032ea541a32bf6749e10c8b9344c92061724c4e751600f886f4732"- s = "0x1542b6457e91098682138856165381453b3d0acae2470286fd8c8a09914b1b5d"- in SolidityCall- (Text.unlines- [ "bytes32 h = keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", d));"- , "x = ecrecover(h, a, b, c);"- ])- [ AbiUInt 8 28- , AbiBytes 32 (hexText r)- , AbiBytes 32 (hexText s)- , AbiBytes 32 (hexText h)- ]- ===>- AbiAddress 0x2d5e56d45c63150d937f2182538a0f18510cb11f- ]-- , testGroup "Precompiled contracts" $+ , testGroup "Precompiled contracts" [ testGroup "Example (reverse)" [ testCase "success" $ assertEqual "example contract reverses"@@ -105,7 +102,6 @@ (execute 1 (h <> v <> r <> s) 32) ] ]- , testGroup "Byte/word manipulations" [ testProperty "padLeft length" $ \n (Bytes bs) -> BS.length (padLeft n bs) == max n (BS.length bs)@@ -120,11 +116,417 @@ y = BS.replicate n 0 in x == y ]- ] + , testGroup "RLP encodings"+ [ testProperty "rlp decode is a retraction (bytes)" $ \(Bytes bs) ->+-- withMaxSuccess 100000 $+ rlpdecode (rlpencode (BS bs)) == Just (BS bs)+ , testProperty "rlp encode is a partial inverse (bytes)" $ \(Bytes bs) ->+-- withMaxSuccess 100000 $+ case rlpdecode bs of+ Just r -> rlpencode r == bs+ Nothing -> True+ , testProperty "rlp decode is a retraction (RLP)" $ \(RLPData r) ->+-- withMaxSuccess 100000 $+ rlpdecode (rlpencode r) == Just r+ ]+ , testGroup "Merkle Patricia Trie"+ [ testProperty "update followed by delete is id" $ \(Bytes r, Bytes s, Bytes t) ->+ whenFail+ (putStrLn ("r:" <> (show (ByteStringS r))) >>+ putStrLn ("s:" <> (show (ByteStringS s))) >>+ putStrLn ("t:" <> (show (ByteStringS t)))) $+-- withMaxSuccess 100000 $+ Patricia.insertValues [(r, BS.pack[1]), (s, BS.pack[2]), (t, BS.pack[3]),+ (r, mempty), (s, mempty), (t, mempty)]+ === (Just $ Literal Patricia.Empty)+ ]++ , testGroup "Symbolic execution"+ [+ -- Somewhat tautological since we are asserting the precondition+ -- on the same form as the actual "requires" clause.+ testCase "SafeAdd success case" $ do+ Just safeAdd <- solcRuntime "SafeAdd"+ [i|+ contract SafeAdd {+ function add(uint x, uint y) public pure returns (uint z) {+ require((z = x + y) >= x);+ }+ }+ |]+ let asWord :: [SWord 8] -> SWord 256+ asWord = fromBytes+ pre preVM = let SymbolicBuffer bs = ditch 4 (fst $ view (state . calldata) preVM)+ (x, y) = splitAt 32 bs+ in asWord x .<= asWord x + asWord y+ .&& view (state . callvalue) preVM .== 0+ post = Just $ \(prestate, poststate) ->+ let SymbolicBuffer input = fst $ view (state.calldata) prestate+ (x, y) = splitAt 32 (drop 4 input)+ in case view result poststate of+ Just (VMSuccess (SymbolicBuffer out)) -> (asWord out) .== (asWord x) + (asWord y)+ _ -> sFalse+ Left (_, res) <- runSMT $ query $ verifyContract (RuntimeCode safeAdd) Nothing (Just ("add(uint256,uint256)", [AbiUIntType 256, AbiUIntType 256])) [] SymbolicS pre post+ putStrLn $ "successfully explored: " <> show (length res) <> " paths"+ ,++ testCase "x == y => x + y == 2 * y" $ do+ Just safeAdd <- solcRuntime "SafeAdd"+ [i|+ contract SafeAdd {+ function add(uint x, uint y) public pure returns (uint z) {+ require((z = x + y) >= x);+ }+ }+ |]+ let asWord :: [SWord 8] -> SWord 256+ asWord = fromBytes+ pre preVM = let SymbolicBuffer bs = ditch 4 (fst $ view (state . calldata) preVM)+ (x, y) = splitAt 32 bs+ in (asWord x .<= asWord x + asWord y)+ .&& (x .== y)+ .&& view (state . callvalue) preVM .== 0+ post = Just $ \(prestate, poststate)+ -> let SymbolicBuffer input = fst $ view (state.calldata) prestate+ (_, y) = splitAt 32 (drop 4 input)+ in case view result poststate of+ Just (VMSuccess (SymbolicBuffer out)) -> asWord out .== 2 * asWord y+ _ -> sFalse+ Left (_, res) <- runSMTWith z3 $ query $+ verifyContract (RuntimeCode safeAdd) Nothing (Just ("add(uint256,uint256)", [AbiUIntType 256, AbiUIntType 256])) [] SymbolicS pre post+ putStrLn $ "successfully explored: " <> show (length res) <> " paths"+ ,+ testCase "factorize 973013" $ do+ Just factor <- solcRuntime "PrimalityCheck"+ [i|+ contract PrimalityCheck {+ function factor(uint x, uint y) public pure {+ require(1 < x && x < 973013 && 1 < y && y < 973013);+ assert(x*y != 973013);+ }+ }+ |]+ bs <- runSMTWith cvc4 $ query $ do+ Right vm <- checkAssert (RuntimeCode factor) Nothing (Just ("factor(uint256,uint256)", [AbiUIntType 256, AbiUIntType 256])) []+ case view (state . calldata . _1) vm of+ SymbolicBuffer bs -> BS.pack <$> mapM (getValue.fromSized) bs+ ConcreteBuffer _ -> error "unexpected"++ let AbiTuple xy = decodeAbiValue (AbiTupleType $ Vector.fromList [AbiUIntType 256, AbiUIntType 256]) (BS.fromStrict (BS.drop 4 bs))+ [AbiUInt 256 x, AbiUInt 256 y] = Vector.toList xy+ assertEqual "" True (x == 953 && y == 1021 || x == 1021 && y == 953)+ ,+ testCase "summary storage writes" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ uint x;+ function f(uint256 y) public {+ x += y;+ x += y;+ }+ }+ |]+ let pre vm = 0 .== view (state . callvalue) vm+ post = Just $ \(prestate, poststate) ->+ let SymbolicBuffer y = ditch 4 $ fst (view (state.calldata) prestate)+ this = view (state . codeContract) prestate+ Just preC = view (env.contracts . at this) prestate+ Just postC = view (env.contracts . at this) poststate+ Symbolic prestore = _storage preC+ Symbolic poststore = _storage postC+ prex = readArray prestore 0+ postx = readArray poststore 0+ in case view result poststate of+ Just (VMSuccess _) -> prex + 2 * (fromBytes y) .== postx+ _ -> sFalse+ Left (_, res) <- runSMT $ query $ verifyContract (RuntimeCode c) Nothing (Just ("f(uint256)", [AbiUIntType 256])) [] SymbolicS pre post+ putStrLn $ "successfully explored: " <> show (length res) <> " paths"+ ,+ -- Inspired by these `msg.sender == to` token bugs+ -- which break linearity of totalSupply.+ testCase "catch storage collisions" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint x, uint y) public {+ assembly {+ let newx := sub(sload(x), 1)+ let newy := add(sload(y), 1)+ sstore(x,newx)+ sstore(y,newy)+ }+ }+ }+ |]+ let pre vm = 0 .== view (state . callvalue) vm+ post = Just $ \(prestate, poststate) ->+ let SymbolicBuffer bs = fst (view (state.calldata) prestate)+ (x,y) = over both (fromBytes) (splitAt 32 $ drop 4 bs)+ this = view (state . codeContract) prestate+ (Just preC, Just postC) = both' (view (env.contracts . at this)) (prestate, poststate)+ --Just postC = view (env.contracts . at this) poststate+ (Symbolic prestore, Symbolic poststore) = both' (view storage) (preC, postC)+ (prex, prey) = both' (readArray prestore) (x, y)+ (postx, posty) = both' (readArray poststore) (x, y)+ in case view result poststate of+ Just (VMSuccess _) -> prex + prey .== postx + (posty :: SWord 256)+ _ -> sFalse+ bs <- runSMT $ query $ do+ Right vm <- verifyContract (RuntimeCode c) Nothing (Just ("f(uint256,uint256)", [AbiUIntType 256, AbiUIntType 256])) [] SymbolicS pre post+ case view (state . calldata . _1) vm of+ SymbolicBuffer bs -> BS.pack <$> mapM (getValue.fromSized) bs+ ConcreteBuffer bs -> error "unexpected"++ let AbiTuple xyz = decodeAbiValue (AbiTupleType $ Vector.fromList [AbiUIntType 256, AbiUIntType 256]) (BS.fromStrict (BS.drop 4 bs))+ [AbiUInt 256 x, AbiUInt 256 y] = Vector.toList xyz+ assertEqual "Catch storage collisions" x y+ ,+ testCase "Deposit contract loop (z3)" $ do+ Just c <- solcRuntime "Deposit"+ [i|+ contract Deposit {+ function deposit(uint256 deposit_count) external pure {+ require(deposit_count < 2**32 - 1);+ ++deposit_count;+ bool found = false;+ for (uint height = 0; height < 32; height++) {+ if ((deposit_count & 1) == 1) {+ found = true;+ break;+ }+ deposit_count = deposit_count >> 1;+ }+ assert(found);+ }+ }+ |]+ Left (_, res) <- runSMTWith z3 $ query $ checkAssert (RuntimeCode c) Nothing (Just ("deposit(uint256)", [AbiUIntType 256])) []+ putStrLn $ "successfully explored: " <> show (length res) <> " paths"+ ,+ testCase "Deposit contract loop (cvc4)" $ do+ Just c <- solcRuntime "Deposit"+ [i|+ contract Deposit {+ function deposit(uint256 deposit_count) external pure {+ require(deposit_count < 2**32 - 1);+ ++deposit_count;+ bool found = false;+ for (uint height = 0; height < 32; height++) {+ if ((deposit_count & 1) == 1) {+ found = true;+ break;+ }+ deposit_count = deposit_count >> 1;+ }+ assert(found);+ }+ }+ |]+ Left (_, res) <- runSMTWith cvc4 $ query $ checkAssert (RuntimeCode c) Nothing (Just ("deposit(uint256)", [AbiUIntType 256])) []+ putStrLn $ "successfully explored: " <> show (length res) <> " paths"+ ,+ testCase "Deposit contract loop (error version)" $ do+ Just c <- solcRuntime "Deposit"+ [i|+ contract Deposit {+ function deposit(uint8 deposit_count) external pure {+ require(deposit_count < 2**32 - 1);+ ++deposit_count;+ bool found = false;+ for (uint height = 0; height < 32; height++) {+ if ((deposit_count & 1) == 1) {+ found = true;+ break;+ }+ deposit_count = deposit_count >> 1;+ }+ assert(found);+ }+ }+ |]+ bs <- runSMT $ query $ do+ Right vm <- checkAssert (RuntimeCode c) Nothing (Just ("deposit(uint8)", [AbiUIntType 8])) []+ case view (state . calldata . _1) vm of+ SymbolicBuffer bs -> BS.pack <$> mapM (getValue.fromSized) bs+ ConcreteBuffer _ -> error "unexpected"++ let deposit = decodeAbiValue (AbiUIntType 8) (BS.fromStrict (BS.drop 4 bs))+ assertEqual "overflowing uint8" deposit (AbiUInt 8 255)+ ,+ -- This test uses cvc4 instead of z3+ testCase "explore function dispatch" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint x) public pure returns (uint) {+ return x;+ }+ }+ |]+ Left (_, res) <- runSMTWith cvc4 $ query $ checkAssert (RuntimeCode c) Nothing Nothing []+ putStrLn $ "successfully explored: " <> show (length res) <> " paths"+ ,++ testCase "injectivity of keccak (32 bytes)" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint x, uint y) public pure {+ if (keccak256(abi.encodePacked(x)) == keccak256(abi.encodePacked(y))) assert(x == y);+ }+ }+ |]+ Left (_, res) <- runSMTWith cvc4 $ query $ checkAssert (RuntimeCode c) Nothing Nothing []+ putStrLn $ "successfully explored: " <> show (length res) <> " paths"+ ,+ testCase "injectivity of keccak (32 bytes)" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint x, uint y) public pure {+ if (keccak256(abi.encodePacked(x)) == keccak256(abi.encodePacked(y))) assert(x == y);+ }+ }+ |]+ Left (_, res) <- runSMTWith cvc4 $ query $ checkAssert (RuntimeCode c) Nothing Nothing []+ putStrLn $ "successfully explored: " <> show (length res) <> " paths"+ ,++ testCase "injectivity of keccak (64 bytes)" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f(uint x, uint y, uint w, uint z) public pure {+ assert (keccak256(abi.encodePacked(x,y)) != keccak256(abi.encodePacked(w,z)));+ }+ }+ |]+ bs <- runSMTWith z3 $ query $ do+ Right vm <- checkAssert (RuntimeCode c) Nothing (Just ("f(uint256,uint256,uint256,uint256)", replicate 4 (AbiUIntType 256))) []+ case view (state . calldata . _1) vm of+ SymbolicBuffer bs -> BS.pack <$> mapM (getValue.fromSized) bs+ ConcreteBuffer _ -> error "unexpected"+ + let AbiTuple xywz = decodeAbiValue (AbiTupleType $ Vector.fromList+ [AbiUIntType 256, AbiUIntType 256,+ AbiUIntType 256, AbiUIntType 256]) (BS.fromStrict (BS.drop 4 bs))+ [AbiUInt 256 x, AbiUInt 256 y, AbiUInt 256 w, AbiUInt 256 z] = Vector.toList xywz+ assertEqual "x == w" x w+ assertEqual "y == z" y z+ ,++ testCase "calldata beyond calldatasize is 0 (z3)" $ do+ Just c <- solcRuntime "A"+ [i|+ contract A {+ function f() public pure {+ uint y;+ assembly {+ let x := calldatasize()+ y := calldataload(x)+ }+ assert(y == 0);+ }+ }+ |]+ Left (_, res) <- runSMTWith z3 $ query $ checkAssert (RuntimeCode c) Nothing Nothing []+ putStrLn $ "successfully explored: " <> show (length res) <> " paths"++ ,++ testCase "keccak soundness" $ do+ Just c <- solcRuntime "C"+ [i|+ contract C {+ mapping (uint => mapping (uint => uint)) maps;++ function f(uint x, uint y) public view {+ assert(maps[y][0] == maps[x][0]);+ }+ }+ |]+ -- should find a counterexample+ Right counterexample <- runSMTWith cvc4 $ query $ checkAssert (RuntimeCode c) Nothing Nothing []+ putStrLn $ "found counterexample:"+++ ,+ testCase "multiple contracts" $ do+ let code =+ [i|+ contract C {+ uint x;+ A constant a = A(0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B);+ + function call_A() public view {+ // should fail since a.x() can be anything+ assert(a.x() == x);+ }+ }+ contract A {+ uint public x;+ }+ |]+ aAddr = Addr 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B+ Just c <- solcRuntime "C" code+ Just a <- solcRuntime "A" code+ Right cex <- runSMT $ query $ do+ vm0 <- abstractVM (Just ("call_A()", [])) [] c SymbolicS+ store <- freshArray (show aAddr) Nothing+ let vm = vm0+ & set (state . callvalue) 0+ & over (env . contracts)+ (Map.insert aAddr (initialContract (RuntimeCode a) &+ set EVM.storage (Symbolic store)))+ verify vm Nothing Nothing (Just checkAssertions)+ putStrLn $ "found counterexample:"++ ]+ , testGroup "Equivalence checking"+ [+ testCase "yul optimized" $ do+ -- These yul programs are not equivalent: (try --calldata $(seth --to-uint256 2) for example)+ -- A: B:+ -- { {+ -- calldatacopy(0, 0, 32) calldatacopy(0, 0, 32)+ -- switch mload(0) switch mload(0)+ -- case 0 { } case 0 { }+ -- case 1 { } case 2 { }+ -- default { invalid() } default { invalid() }+ -- } }+ let aPrgm = hex "602060006000376000805160008114601d5760018114602457fe6029565b8191506029565b600191505b50600160015250"+ bPrgm = hex "6020600060003760005160008114601c5760028114602057fe6021565b6021565b5b506001600152"+ runSMTWith z3 $ query $ do+ Right counterexample <- equivalenceCheck aPrgm bPrgm Nothing Nothing+ return ()++ ]+ ] where (===>) = assertSolidityComputation +runSimpleVM :: ByteString -> ByteString -> Maybe ByteString+runSimpleVM x ins = case loadVM x of+ Nothing -> Nothing+ Just vm -> let calldata' = (ConcreteBuffer ins, literal . num $ BS.length ins)+ in case runState (assign (state.calldata) calldata' >> exec) vm of+ (VMSuccess (ConcreteBuffer bs), _) -> Just bs+ _ -> Nothing++loadVM :: ByteString -> Maybe VM+loadVM x =+ case runState exec (vmForEthrunCreation x) of+ (VMSuccess (ConcreteBuffer targetCode), vm1) -> do+ let target = view (state . contract) vm1+ vm2 = execState (replaceCodeOfSelf (RuntimeCode targetCode)) vm1+ return $ snd $ flip runState vm2+ (do resetState+ assign (state . gas) 0xffffffffffffffff -- kludge+ loadContract target)+ _ -> Nothing+ hex :: ByteString -> ByteString hex s = case Hex.decode s of@@ -148,6 +550,11 @@ then "memory" else "" +runFunction :: Text -> ByteString -> IO (Maybe ByteString)+runFunction c input = do+ Just x <- singleContract "X" c+ return $ runSimpleVM x input+ runStatements :: Text -> [AbiValue] -> AbiType -> IO (Maybe ByteString)@@ -158,34 +565,15 @@ <> " " <> defaultDataLocation (abiValueType x) <> " " <> Text.pack [c]) (zip args "abcdefg"))- sig =+ s = "foo(" <> Text.intercalate "," (map (abiTypeSolidity . abiValueType) args) <> ")" - Just x <- singleContract "X" [i|+ runFunction [i| function foo(${params}) public pure returns (${abiTypeSolidity t} x) { ${stmts} }- |]-- case runState exec (vmForEthrunCreation x) of- (VMSuccess targetCode, vm1) -> do- let target = view (state . contract) vm1- vm2 = execState (replaceCodeOfSelf targetCode) vm1- case flip runState vm2- (do resetState- assign (state . gas) 0xffffffffffffffff -- kludge- loadContract target- assign (state . calldata)- (abiCalldata sig (Vector.fromList args))- exec) of- (VMSuccess out, _) ->- return (Just out)- (VMFailure problem, _) -> do- print problem- return Nothing- _ ->- return Nothing+ |] (abiCalldata s (Vector.fromList args)) newtype Bytes = Bytes ByteString deriving Eq@@ -195,6 +583,21 @@ instance Arbitrary Bytes where arbitrary = fmap (Bytes . BS.pack) arbitrary++newtype RLPData = RLPData RLP+ deriving (Eq, Show)++-- bias towards bytestring to try to avoid infinite recursion+instance Arbitrary RLPData where+ arbitrary = frequency+ [(5, do+ Bytes bytes <- arbitrary+ return $ RLPData $ BS bytes)+ , (1, do+ k <- choose (0,10)+ ls <- vectorOf k arbitrary+ return $ RLPData $ List [r | RLPData r <- ls])+ ] data Invocation = SolidityCall Text [AbiValue]