From dd1ae7a9dad2b73de3e1219c8fe91401875aacc2 Mon Sep 17 00:00:00 2001 From: David Terry Date: Thu, 29 Jul 2021 12:34:22 +0200 Subject: [PATCH 1/6] hevm: add env var to control nonce --- nix/build-dapp-package.nix | 6 ++- src/dapp-tests/default.nix | 39 ++++++++++++++++--- src/dapp-tests/env/origin.sol | 8 ++++ src/dapp-tests/env/rest.sol | 72 +++++++++++++++++++++++++++++++++++ src/hevm/CHANGELOG.md | 4 ++ src/hevm/README.md | 1 + src/hevm/src/EVM/UnitTest.hs | 42 +++++++++++--------- 7 files changed, 147 insertions(+), 25 deletions(-) create mode 100644 src/dapp-tests/env/origin.sol create mode 100644 src/dapp-tests/env/rest.sol diff --git a/nix/build-dapp-package.nix b/nix/build-dapp-package.nix index d091a46b0..0418a9a99 100644 --- a/nix/build-dapp-package.nix +++ b/nix/build-dapp-package.nix @@ -23,8 +23,9 @@ in , deps ? [] , solc ? "${pkgs.solc}/bin/solc" , shouldFail ? false - , dappFlags ? "" , doCheck ? true + , dapprc ? "" + , testFlags ? "" , ... }@args: pkgs.stdenv.mkDerivation ( rec { inherit doCheck; @@ -47,6 +48,7 @@ in '') passthru.libPaths; buildPhase = '' + source ${pkgs.writeText "dapprc" dapprc} mkdir -p out export DAPP_SOLC=${solc} export DAPP_REMAPPINGS="$REMAPPINGS" @@ -59,7 +61,7 @@ in ''; checkPhase = let - cmd = "DAPP_SKIP_BUILD=1 dapp test ${dappFlags}"; + cmd = "DAPP_SKIP_BUILD=1 dapp test ${testFlags}"; in if shouldFail then "${cmd} && exit 1 || echo 0" diff --git a/src/dapp-tests/default.nix b/src/dapp-tests/default.nix index 3c44e1473..7c7cdf493 100644 --- a/src/dapp-tests/default.nix +++ b/src/dapp-tests/default.nix @@ -114,11 +114,10 @@ let deps = [ ds-test ds-thing ]; }; - runTest = { dir, shouldFail, name, dappFlags?"" }: pkgs.buildDappPackage { - inherit name shouldFail; + runTest = { dir, shouldFail, name, dapprc ? "", testFlags ? "" }: pkgs.buildDappPackage { + inherit name shouldFail testFlags dapprc; solc=solc-0_6_7; src = dir; - dappFlags = "${dappFlags}"; deps = [ ds-test ds-token ds-math ]; checkInputs = with pkgs; [ hevm jq seth dapp solc ]; }; @@ -128,7 +127,35 @@ in dir = ./pass; name = "dappTestsShouldPass"; shouldFail = false; - dappFlags = "--max-iterations 50 --smttimeout 600000 --ffi"; + testFlags = "--max-iterations 50 --smttimeout 600000 --ffi"; + }; + + envVars = let + envVarTest = match : dapprc : runTest { + testFlags = "--match ${match}"; + dir = ./env; + name = "dappTestEnvVar"; + shouldFail = false; + inherit dapprc; + }; + seth = "${pkgs.seth}/bin/seth"; + in pkgs.recurseIntoAttrs { + # we get "hevm: insufficient balance for gas cost" if we run these together... + origin = envVarTest "origin.sol" "export DAPP_TEST_ORIGIN=$(${seth} --to-hex 256)"; + rest = envVarTest "rest.sol" '' + export DAPP_TEST_BALANCE=$(${seth} --to-wei 998877665544 ether) + export DAPP_TEST_ADDRESS=$(${seth} --to-hex 256) + export DAPP_TEST_CALLER=$(${seth} --to-hex 100) + export DAPP_TEST_GAS_CREATE=$(${seth} --to-wei 4.20 ether) + export DAPP_TEST_GAS_CALL=$(${seth} --to-wei 0.69 ether) + export DAPP_TEST_NONCE=100 + export DAPP_TEST_COINBASE=$(${seth} --to-hex 666) + export DAPP_TEST_NUMBER=420 + export DAPP_TEST_TIMESTAMP=69 + export DAPP_TEST_GAS_LIMIT=$(${seth} --to-wei 4206966 ether) + export DAPP_TEST_GAS_PRICE=100 + export DAPP_TEST_DIFFICULTY=600 + ''; }; shouldFail = let @@ -136,7 +163,7 @@ in dir = ./fail; shouldFail = true; name = "dappTestsShouldFail-${match}"; - dappFlags = "--match ${match} --smttimeout 600000"; + testFlags = "--match ${match} --smttimeout 600000"; }; in pkgs.recurseIntoAttrs { prove-add = fail "prove_add"; @@ -155,7 +182,7 @@ in solc = solc-0_6_7; src = dss-src; name = "dss"; - dappFlags = "--match '[^dai].t.sol'"; + testFlags = "--match '[^dai].t.sol'"; deps = [ ds-test ds-token ds-value ]; }; } diff --git a/src/dapp-tests/env/origin.sol b/src/dapp-tests/env/origin.sol new file mode 100644 index 000000000..5ca772f8c --- /dev/null +++ b/src/dapp-tests/env/origin.sol @@ -0,0 +1,8 @@ +import "ds-test/test.sol"; + +contract Env is DSTest { + // DAPP_TEST_ORIGIN + function testOrigin() public { + assertEq(tx.origin, address(256)); + } +} diff --git a/src/dapp-tests/env/rest.sol b/src/dapp-tests/env/rest.sol new file mode 100644 index 000000000..367075e2a --- /dev/null +++ b/src/dapp-tests/env/rest.sol @@ -0,0 +1,72 @@ +import "ds-test/test.sol"; + +contract Env is DSTest { + uint creationGas; + constructor() public { + creationGas = gasleft(); + } + + // TODO: why does this fail when address == 0? + // DAPP_TEST_BALANCE + function testBalance() public { + assertEq(address(this).balance, 998877665544 ether); + } + // DAPP_TEST_ADDRESS + function testAddress() public { + assertEq(address(this), address(256)); + } + // DAPP_TEST_NONCE + // we can't test the nonce directly, but can instead check the address of a newly deployed contract + function testNonce() public { + uint8 nonce = 100; + bytes memory payload = abi.encodePacked(hex"d694", address(this), nonce); + + address expected = address(uint160(uint256(keccak256(payload)))); + address actual = address(new Trivial()); + assertEq(actual, expected); + + } + // DAPP_TEST_CALLER + function testCaller() public { + assertEq(msg.sender, address(100)); + } + // DAPP_TEST_GAS_CREATE + function testGasCreate() public { + // we can't be exact since we had to spend some gas to write to storage... + assertLt(creationGas, 4.20 ether); + assertGt(creationGas, 4.1999999999999 ether); + } + // DAPP_TEST_GAS_CALL + function testGasCall() public { + uint gas = gasleft(); + // we can't be exact since we had to spend some gas to get here... + assertLt(gas, 0.69 ether); + assertGt(gas, 0.689999999999999 ether); + } + // DAPP_TEST_COINBASE + function testCoinbase() public { + assertEq(block.coinbase, address(666)); + } + // DAPP_TEST_NUMBER + function testBlockNumber() public { + assertEq(block.number, 420); + } + // DAPP_TEST_TIMESTAMP + function testTimestamp() public { + assertEq(block.timestamp, 69); + } + // DAPP_TEST_GAS_LIMIT + function testGasLimit() public { + assertEq(block.gaslimit, 4206966 ether); + } + // DAPP_TEST_GAS_PRICE + function testGasPrice() public { + assertEq(tx.gasprice, 100); + } + // DAPP_TEST_DIFFICULTY + function testDifficulty() public { + assertEq(block.difficulty, 600); + } +} + +contract Trivial {} diff --git a/src/hevm/CHANGELOG.md b/src/hevm/CHANGELOG.md index 6c4ad5f40..83f82fd7f 100644 --- a/src/hevm/CHANGELOG.md +++ b/src/hevm/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Added + +- A new configuration variable `DAPP_TEST_NONCE` has been added that allows control over the nonce of the testing contract + ### Changed - The configuration variable `DAPP_TEST_BALANCE_CREATE` has been renamed to `DAPP_TEST_BALANCE` diff --git a/src/hevm/README.md b/src/hevm/README.md index ddd02888a..a237256e2 100644 --- a/src/hevm/README.md +++ b/src/hevm/README.md @@ -239,6 +239,7 @@ These environment variables can be used to control block parameters: | `DAPP_TEST_GAS_CREATE` | `0xffffffffffff` | The gas to provide when creating the testing contract | | `DAPP_TEST_GAS_CALL` | `0xffffffffffff` | The gas to provide to each call made to the testing contract | | `DAPP_TEST_BALANCE` | `0xffffffffffffffffffffffff` | The balance to provide to `DAPP_TEST_ADDRESS` | +| `DAPP_TEST_NONCE ` | `1` | The initial nonce to use for `DAPP_TEST_ADDRESS` | | `DAPP_TEST_COINBASE` | `0x0000000000000000000000000000000000000000` | The coinbase address. Will be set to the coinbase for the block at `DAPP_TEST_NUMBER` if rpc is enabled | | `DAPP_TEST_NUMBER` | `0` | The block number. Will be set to the latest block if rpc is enabled | | `DAPP_TEST_TIMESTAMP` | `0` | The block timestamp. Will be set to the timestamp for the block at `DAPP_TEST_NUMBER` if rpc is enabled | diff --git a/src/hevm/src/EVM/UnitTest.hs b/src/hevm/src/EVM/UnitTest.hs index e7eae8e70..77582499c 100644 --- a/src/hevm/src/EVM/UnitTest.hs +++ b/src/hevm/src/EVM/UnitTest.hs @@ -87,20 +87,21 @@ data UnitTestOptions = UnitTestOptions } data TestVMParams = TestVMParams - { testAddress :: Addr - , testCaller :: Addr - , testOrigin :: Addr - , testGasCreate :: W256 - , testGasCall :: W256 - , testBalanceCreate :: W256 - , testCoinbase :: Addr - , testNumber :: W256 - , testTimestamp :: W256 - , testGaslimit :: W256 - , testGasprice :: W256 - , testMaxCodeSize :: W256 - , testDifficulty :: W256 - , testChainId :: W256 + { testAddress :: Addr + , testNonce :: W256 + , testCaller :: Addr + , testOrigin :: Addr + , testGasCreate :: W256 + , testGasCall :: W256 + , testBalance :: W256 + , testCoinbase :: Addr + , testNumber :: W256 + , testTimestamp :: W256 + , testGaslimit :: W256 + , testGasprice :: W256 + , testMaxCodeSize :: W256 + , testDifficulty :: W256 + , testChainId :: W256 } defaultGasForCreating :: W256 @@ -112,6 +113,9 @@ defaultGasForInvoking = 0xffffffffffff defaultBalanceForTestContract :: W256 defaultBalanceForTestContract = 0xffffffffffffffffffffffff +defaultNonceForTestContract :: W256 +defaultNonceForTestContract = 1 + defaultMaxCodeSize :: W256 defaultMaxCodeSize = 0xffffffff @@ -135,7 +139,10 @@ initializeUnitTest UnitTestOptions { .. } theContract = do Stepper.evm $ do -- Give a balance to the test target - env . contracts . ix addr . balance += w256 (testBalanceCreate testParams) + env . contracts . ix addr . balance += w256 (testBalance testParams) + + -- Set the test targets nonce + env . contracts . ix addr . nonce .= w256 (testNonce testParams) -- call setUp(), if it exists, to initialize the test contract let theAbi = view abiMap theContract @@ -927,8 +934,8 @@ initialUnitTestVm (UnitTestOptions {..}) theContract = } creator = initialContract (RuntimeCode mempty) - & set nonce 1 - & set balance (w256 testBalanceCreate) + & set nonce (w256 testNonce) + & set balance (w256 testBalance) in vm & set (env . contracts . at ethrunAddress) (Just creator) @@ -967,6 +974,7 @@ getParametersFromEnvironmentVariables rpc = do TestVMParams <$> getAddr "DAPP_TEST_ADDRESS" (createAddress ethrunAddress 1) + <*> getWord "DAPP_TEST_NONCE" defaultNonceForTestContract <*> getAddr "DAPP_TEST_CALLER" ethrunAddress <*> getAddr "DAPP_TEST_ORIGIN" ethrunAddress <*> getWord "DAPP_TEST_GAS_CREATE" defaultGasForCreating From 14ca8312adcd690a8c9437f393e35e3ccf4ed543 Mon Sep 17 00:00:00 2001 From: David Terry Date: Thu, 29 Jul 2021 16:16:00 +0200 Subject: [PATCH 2/6] nix: buildDappPackage: use real dapprc --- nix/build-dapp-package.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/build-dapp-package.nix b/nix/build-dapp-package.nix index 0418a9a99..55fe5ea9b 100644 --- a/nix/build-dapp-package.nix +++ b/nix/build-dapp-package.nix @@ -48,7 +48,7 @@ in '') passthru.libPaths; buildPhase = '' - source ${pkgs.writeText "dapprc" dapprc} + ln -s ${pkgs.writeText "dapprc" dapprc} ./.dapprc mkdir -p out export DAPP_SOLC=${solc} export DAPP_REMAPPINGS="$REMAPPINGS" From 4f4bd4089821c7e3f262e94114ddc09186012bb0 Mon Sep 17 00:00:00 2001 From: David Terry Date: Fri, 30 Jul 2021 14:33:10 +0200 Subject: [PATCH 3/6] hevm: add look and file cheatcodes --- src/dapp-tests/pass/cheatCodes.sol | 56 +++++++++++++++++++++ src/hevm/src/EVM.hs | 79 +++++++++++++++++++++++++++++- 2 files changed, 133 insertions(+), 2 deletions(-) diff --git a/src/dapp-tests/pass/cheatCodes.sol b/src/dapp-tests/pass/cheatCodes.sol index 1305284d2..87ed73645 100644 --- a/src/dapp-tests/pass/cheatCodes.sol +++ b/src/dapp-tests/pass/cheatCodes.sol @@ -11,6 +11,12 @@ interface Hevm { function sign(uint256,bytes32) external returns (uint8,bytes32,bytes32); function addr(uint256) external returns (address); function ffi(string[] calldata) external returns (bytes memory); + function file(bytes32,uint) external; + function file(bytes32,address) external; + function file(bytes32,address,uint) external; + function file(bytes32,address,address) external; + function look(bytes32) external returns (uint); + function look(bytes32,address) external returns (uint); } contract HasStorage { @@ -87,4 +93,54 @@ contract CheatCodes is DSTest { (string memory output) = abi.decode(hevm.ffi(inputs), (string)); assertEq(output, "acab"); } + + function testFileNonce(address who, uint n) public { + hevm.file("nonce", who, n); + assertEq(hevm.look("nonce", who), n); + } + + function testBalance(address who, uint bal) public { + hevm.file("balance", who, bal); + assertEq(who.balance, bal); + } + + function testAddress(address next) public { + hevm.file("address", address(this), next); + assertEq(address(this), next); + } + + function proveCaller(address who) public { + hevm.file("caller", who); + assertEq(msg.sender, who); + } + + function testOrigin(address who) public { + hevm.file("origin", who); + assertEq(tx.origin, who); + } + + function testCoinbase(address who) public { + hevm.file("coinbase", who); + assertEq(block.coinbase, who); + } + + function testGasPrice(uint price) public { + hevm.file("gasPrice", price); + assertEq(tx.gasprice, price); + } + + function testTxGasLimit(uint limit) public { + hevm.file("txGasLimit", limit); + assertEq(hevm.look("txGasLimit"), limit); + } + + function testBlockGasLimit(uint limit) public { + hevm.file("blockGasLimit", limit); + assertEq(block.gaslimit, limit); + } + + function testBlockGasLimit(uint difficulty) public { + hevm.file("difficulty", difficulty); + assertEq(block.difficulty, difficulty); + } } diff --git a/src/hevm/src/EVM.hs b/src/hevm/src/EVM.hs index 33dd718e2..ce405eeec 100644 --- a/src/hevm/src/EVM.hs +++ b/src/hevm/src/EVM.hs @@ -52,7 +52,6 @@ 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 as V import qualified Data.Vector.Storable as Vector import qualified Data.Vector.Storable.Mutable as Vector @@ -2012,6 +2011,82 @@ cheatActions = assign (state . memory . word256At outOffset) res _ -> vmError (BadCheatCode sig), + action "file(bytes32,address,uint256)" $ + \sig _ _ input -> case decodeStaticArgs input of + [what', addr, val] -> forceConcrete what' $ \(C _ (decodeUtf8 . BS.filter (/= 0) . word256Bytes -> what)) -> + makeUnique addr $ \(C _ (num -> who)) -> + case what of + "nonce" -> forceConcrete val $ \v -> assign (env . contracts . ix who . nonce) v + "balance" -> forceConcrete val $ \v -> assign (env . contracts . ix who . balance) v + _ -> vmError (BadCheatCode sig) + _ -> vmError (BadCheatCode sig), + + action "file(bytes32,address,address)" $ + \sig _ _ input -> case decodeStaticArgs input of + [what', old', new'] -> forceConcrete what' $ \(C _ (decodeUtf8 . BS.filter (/= 0) . word256Bytes -> what)) -> + makeUnique old' $ \(C _ (num -> old)) -> + makeUnique new' $ \(C _ (num -> new)) -> + case what of + "address" -> do + vm <- get + let cs = view (env . contracts) vm + c = Map.lookup old cs + case c of + Just c' -> assign (env . contracts) $ Map.insert new c' (Map.delete old cs) + Nothing -> vmError (BadCheatCode sig) + _ -> vmError (BadCheatCode sig) + _ -> vmError (BadCheatCode sig), + + action "file(bytes32,address)" $ + \sig _ _ input -> case decodeStaticArgs input of + [what', addr] -> forceConcrete what' $ \(C _ (decodeUtf8 . BS.filter (/= 0) . word256Bytes -> what)) -> + case what of + "caller" -> assign (frames . ix 0 . frameState . caller) (SAddr . sFromIntegral . rawVal $ addr) + "origin" -> makeUnique addr $ \(C _ (num -> who)) -> assign (tx . origin) who + "coinbase" -> makeUnique addr $ \(C _ (num -> who)) -> assign (block . coinbase) who + _ -> vmError (BadCheatCode sig) + _ -> vmError (BadCheatCode sig), + + action "file(bytes32,uint256)" $ + \sig _ _ input -> case decodeStaticArgs input of + [what', val'] -> forceConcrete2 (what', val') $ \((C _ (decodeUtf8 . BS.filter (/= 0) . word256Bytes -> what)), val) -> + case what of + "gasPrice" -> assign (tx . gasprice) val + "txGasLimit" -> assign (tx . txgaslimit) val + "blockGasLimit" -> assign (block . gaslimit) val + "difficulty" -> assign (block . difficulty) val + _ -> vmError (BadCheatCode sig) + _ -> vmError (BadCheatCode sig), + + action "look(bytes32,address)" $ + \sig outOffset _ input -> + case decodeStaticArgs input of + [what', who'] -> forceConcrete what' $ \(C _ (decodeUtf8 . BS.filter (/= 0) . word256Bytes -> what)) -> + makeUnique who' $ \(C _ (num -> who))-> do + case what of + "nonce" -> do + vm <- get + case Map.lookup who $ view (env . contracts) vm of + Just c' -> do + let out = litWord (_nonce c') + assign (state . returndata . word256At 0) out + assign (state . memory . word256At outOffset) out + _ -> vmError (BadCheatCode sig) + _ -> vmError (BadCheatCode sig) + _ -> vmError (BadCheatCode sig), + + action "look(bytes32)" $ + \sig outOffset _ input -> case decodeStaticArgs input of + [what'] -> forceConcrete what' $ \(C _ (decodeUtf8 . BS.filter (/= 0) . word256Bytes -> what)) -> + case what of + "txGasLimit" -> do + vm <- get + let out = litWord $ view (tx . txgaslimit) vm + assign (state . returndata . word256At 0) out + assign (state . memory . word256At outOffset) out + _ -> vmError (BadCheatCode sig) + _ -> vmError (BadCheatCode sig), + action "sign(uint256,bytes32)" $ \sig outOffset _ input -> case decodeStaticArgs input of [sk, hash] -> @@ -2300,7 +2375,7 @@ finishFrame how = do modifying burned (subtract remainingGas) modifying (state . gas) (+ remainingGas) - FeeSchedule {..} = view ( block . schedule ) oldVm + FeeSchedule {} = view ( block . schedule ) oldVm -- Now dispatch on whether we were creating or calling, -- and whether we shall return, revert, or error (six cases). From b934e8ffd964f8ce508df6ee1e7d1103f9b9a05a Mon Sep 17 00:00:00 2001 From: David Terry Date: Fri, 30 Jul 2021 14:33:34 +0200 Subject: [PATCH 4/6] hevm: dev: add some new opts to ghciTest --- src/hevm/src/EVM/Dev.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hevm/src/EVM/Dev.hs b/src/hevm/src/EVM/Dev.hs index 6524f51e5..c34abcb3f 100644 --- a/src/hevm/src/EVM/Dev.hs +++ b/src/hevm/src/EVM/Dev.hs @@ -47,8 +47,8 @@ loadDappInfo path file = _ -> error "nope, sorry" -ghciTest :: String -> String -> Maybe String -> IO [Bool] -ghciTest root path statePath = +ghciTest :: String -> String -> Maybe Text -> Maybe Int -> Maybe String -> IO [Bool] +ghciTest root path match verbosity statePath = withCurrentDirectory root $ do loadFacts <- case statePath of @@ -61,12 +61,12 @@ ghciTest root path statePath = let opts = UnitTestOptions { oracle = EVM.Fetch.zero - , verbose = Nothing + , verbose = verbosity , maxIter = Nothing , smtTimeout = Nothing , smtState = Nothing , solver = Nothing - , match = "" + , match = fromMaybe ".*" match , fuzzRuns = 100 , replay = Nothing , vmModifier = loadFacts @@ -78,7 +78,7 @@ ghciTest root path statePath = readSolc path >>= \case Just (contractMap, _) -> do - let unitTests = findAllUnitTests (Map.elems contractMap) + let unitTests = findUnitTests (EVM.UnitTest.match opts) $ Map.elems contractMap results <- runSMT $ query $ concatMapM (runUnitTestContract opts contractMap) unitTests let (passing, _) = unzip results pure passing From 51fdbaa3f4ff6e789d05a01ea78c879aa378e416 Mon Sep 17 00:00:00 2001 From: David Terry Date: Mon, 2 Aug 2021 21:01:33 +0200 Subject: [PATCH 5/6] hevm: rm address replacement cheatcode, add code replacement cheatcode --- nix/build-dapp-package.nix | 3 +- src/dapp-tests/default.nix | 3 +- src/dapp-tests/pass/cheatCodes.sol | 23 +++++++---- src/hevm/src/EVM.hs | 64 ++++++++++++++---------------- src/hevm/src/EVM/Types.hs | 3 ++ 5 files changed, 53 insertions(+), 43 deletions(-) diff --git a/nix/build-dapp-package.nix b/nix/build-dapp-package.nix index 55fe5ea9b..ad9d7063f 100644 --- a/nix/build-dapp-package.nix +++ b/nix/build-dapp-package.nix @@ -48,8 +48,9 @@ in '') passthru.libPaths; buildPhase = '' - ln -s ${pkgs.writeText "dapprc" dapprc} ./.dapprc mkdir -p out + export LANG=C.UTF-8 + ln -s ${pkgs.writeText "dapprc" dapprc} ./.dapprc export DAPP_SOLC=${solc} export DAPP_REMAPPINGS="$REMAPPINGS" export DAPP_SRC=$src diff --git a/src/dapp-tests/default.nix b/src/dapp-tests/default.nix index 7c7cdf493..e3ae24546 100644 --- a/src/dapp-tests/default.nix +++ b/src/dapp-tests/default.nix @@ -127,7 +127,7 @@ in dir = ./pass; name = "dappTestsShouldPass"; shouldFail = false; - testFlags = "--max-iterations 50 --smttimeout 600000 --ffi"; + testFlags = "--max-iterations 50 --smttimeout 600000 --ffi -v"; }; envVars = let @@ -141,6 +141,7 @@ in seth = "${pkgs.seth}/bin/seth"; in pkgs.recurseIntoAttrs { # we get "hevm: insufficient balance for gas cost" if we run these together... + # maybe we need an env var to set the balance for the origin? origin = envVarTest "origin.sol" "export DAPP_TEST_ORIGIN=$(${seth} --to-hex 256)"; rest = envVarTest "rest.sol" '' export DAPP_TEST_BALANCE=$(${seth} --to-wei 998877665544 ether) diff --git a/src/dapp-tests/pass/cheatCodes.sol b/src/dapp-tests/pass/cheatCodes.sol index 87ed73645..40aa08ab3 100644 --- a/src/dapp-tests/pass/cheatCodes.sol +++ b/src/dapp-tests/pass/cheatCodes.sol @@ -14,17 +14,26 @@ interface Hevm { function file(bytes32,uint) external; function file(bytes32,address) external; function file(bytes32,address,uint) external; - function file(bytes32,address,address) external; function look(bytes32) external returns (uint); function look(bytes32,address) external returns (uint); + function replace(address,bytes calldata) external; } contract HasStorage { uint slot0 = 10; } +contract Old { + uint constant public x = 10; +} + +contract New { + uint constant public x = 100; +} + contract CheatCodes is DSTest { address store = address(new HasStorage()); + Old target = new Old(); Hevm hevm = Hevm(HEVM_ADDRESS); function test_warp_concrete(uint128 jump) public { @@ -94,7 +103,7 @@ contract CheatCodes is DSTest { assertEq(output, "acab"); } - function testFileNonce(address who, uint n) public { + function testNonce(address who, uint n) public { hevm.file("nonce", who, n); assertEq(hevm.look("nonce", who), n); } @@ -104,9 +113,9 @@ contract CheatCodes is DSTest { assertEq(who.balance, bal); } - function testAddress(address next) public { - hevm.file("address", address(this), next); - assertEq(address(this), next); + function testReplace() public { + hevm.replace(address(target), type(New).runtimeCode); + assertEq(New(address(target)).x(), 100); } function proveCaller(address who) public { @@ -139,8 +148,8 @@ contract CheatCodes is DSTest { assertEq(block.gaslimit, limit); } - function testBlockGasLimit(uint difficulty) public { - hevm.file("difficulty", difficulty); + function testDifficulty(uint difficulty) public { + hevm.file(bytes32("difficulty"), difficulty); assertEq(block.difficulty, difficulty); } } diff --git a/src/hevm/src/EVM.hs b/src/hevm/src/EVM.hs index ce405eeec..a91e8c0fa 100644 --- a/src/hevm/src/EVM.hs +++ b/src/hevm/src/EVM.hs @@ -2013,35 +2013,19 @@ cheatActions = action "file(bytes32,address,uint256)" $ \sig _ _ input -> case decodeStaticArgs input of - [what', addr, val] -> forceConcrete what' $ \(C _ (decodeUtf8 . BS.filter (/= 0) . word256Bytes -> what)) -> - makeUnique addr $ \(C _ (num -> who)) -> + [what', addr, val] -> forceConcrete what' $ \(C _ (utf8Word -> what)) -> + makeUnique addr $ \(C _ (num -> usr)) -> fetchAccount usr $ \_ -> case what of - "nonce" -> forceConcrete val $ \v -> assign (env . contracts . ix who . nonce) v - "balance" -> forceConcrete val $ \v -> assign (env . contracts . ix who . balance) v + "nonce" -> forceConcrete val $ \v -> assign (env . contracts . ix usr . nonce) v + "balance" -> forceConcrete val $ \v -> assign (env . contracts . ix usr . balance) v _ -> vmError (BadCheatCode sig) _ -> vmError (BadCheatCode sig), - action "file(bytes32,address,address)" $ - \sig _ _ input -> case decodeStaticArgs input of - [what', old', new'] -> forceConcrete what' $ \(C _ (decodeUtf8 . BS.filter (/= 0) . word256Bytes -> what)) -> - makeUnique old' $ \(C _ (num -> old)) -> - makeUnique new' $ \(C _ (num -> new)) -> - case what of - "address" -> do - vm <- get - let cs = view (env . contracts) vm - c = Map.lookup old cs - case c of - Just c' -> assign (env . contracts) $ Map.insert new c' (Map.delete old cs) - Nothing -> vmError (BadCheatCode sig) - _ -> vmError (BadCheatCode sig) - _ -> vmError (BadCheatCode sig), - action "file(bytes32,address)" $ \sig _ _ input -> case decodeStaticArgs input of - [what', addr] -> forceConcrete what' $ \(C _ (decodeUtf8 . BS.filter (/= 0) . word256Bytes -> what)) -> + [what', addr] -> forceConcrete what' $ \(C _ (utf8Word -> what)) -> case what of - "caller" -> assign (frames . ix 0 . frameState . caller) (SAddr . sFromIntegral . rawVal $ addr) + "caller" -> assign (state . caller) (SAddr . sFromIntegral . rawVal $ addr) "origin" -> makeUnique addr $ \(C _ (num -> who)) -> assign (tx . origin) who "coinbase" -> makeUnique addr $ \(C _ (num -> who)) -> assign (block . coinbase) who _ -> vmError (BadCheatCode sig) @@ -2049,7 +2033,7 @@ cheatActions = action "file(bytes32,uint256)" $ \sig _ _ input -> case decodeStaticArgs input of - [what', val'] -> forceConcrete2 (what', val') $ \((C _ (decodeUtf8 . BS.filter (/= 0) . word256Bytes -> what)), val) -> + [what', val'] -> forceConcrete2 (what', val') $ \((C _ (utf8Word -> what)), val) -> case what of "gasPrice" -> assign (tx . gasprice) val "txGasLimit" -> assign (tx . txgaslimit) val @@ -2061,7 +2045,7 @@ cheatActions = action "look(bytes32,address)" $ \sig outOffset _ input -> case decodeStaticArgs input of - [what', who'] -> forceConcrete what' $ \(C _ (decodeUtf8 . BS.filter (/= 0) . word256Bytes -> what)) -> + [what', who'] -> forceConcrete what' $ \(C _ (utf8Word -> what)) -> makeUnique who' $ \(C _ (num -> who))-> do case what of "nonce" -> do @@ -2077,7 +2061,7 @@ cheatActions = action "look(bytes32)" $ \sig outOffset _ input -> case decodeStaticArgs input of - [what'] -> forceConcrete what' $ \(C _ (decodeUtf8 . BS.filter (/= 0) . word256Bytes -> what)) -> + [what'] -> forceConcrete what' $ \(C _ (utf8Word -> what)) -> case what of "txGasLimit" -> do vm <- get @@ -2087,6 +2071,19 @@ cheatActions = _ -> vmError (BadCheatCode sig) _ -> vmError (BadCheatCode sig), + action "replace(address,bytes)" $ + \sig _ _ input -> case decodeBuffer [AbiAddressType, AbiBytesDynamicType] input of + CAbi vals -> case vals of + [AbiAddress usr, AbiBytesDynamic (ConcreteBuffer -> newCode)] -> do + vm <- get + -- TODO: make error messages nice here + when (usr == (view (state . codeContract) vm)) $ vmError (BadCheatCode sig) + assign (env . contracts . (ix usr) . contractcode) $ RuntimeCode newCode + assign (env . contracts . (ix usr) . codeOps) $ mkCodeOps newCode + assign (env . contracts . (ix usr) . opIxMap) $ mkOpIxMap newCode + _ -> vmError (BadCheatCode sig) + _ -> vmError (BadCheatCode sig), + action "sign(uint256,bytes32)" $ \sig outOffset _ input -> case decodeStaticArgs input of [sk, hash] -> @@ -2627,15 +2624,14 @@ checkJump x xs = do self <- use (state . codeContract) theCodeOps <- use (env . contracts . ix self . codeOps) theOpIxMap <- use (env . contracts . ix self . opIxMap) - if x < num (len theCode) && 0x5b == (fromMaybe (error "tried to jump to symbolic code location") $ unliteral $ EVM.Symbolic.index (num x) theCode) - then - if OpJumpdest == snd (theCodeOps RegularVector.! (theOpIxMap Vector.! num x)) - then do - state . stack .= xs - state . pc .= num x - else - vmError BadJumpDestination - else vmError BadJumpDestination + if x < num (len theCode) + && 0x5b == (fromMaybe (error "tried to jump to symbolic code location") $ unliteral $ EVM.Symbolic.index (num x) theCode) + && OpJumpdest == snd (theCodeOps RegularVector.! (theOpIxMap Vector.! num x)) + then do + state . stack .= xs + state . pc .= num x + else do + vmError BadJumpDestination opSize :: Word8 -> Int opSize x | x >= 0x60 && x <= 0x7f = num x - 0x60 + 2 diff --git a/src/hevm/src/EVM/Types.hs b/src/hevm/src/EVM/Types.hs index 8900fbcb1..9ce96ba46 100644 --- a/src/hevm/src/EVM/Types.hs +++ b/src/hevm/src/EVM/Types.hs @@ -556,3 +556,6 @@ abiKeccak = concatMapM :: Monad m => (a -> m [b]) -> [a] -> m [b] concatMapM f xs = liftM concat (mapM f xs) + +utf8Word :: W256 -> Text +utf8Word = Text.decodeUtf8 . BS.filter (/= 0) . word256Bytes From 7006ccd66af5918ffb876b8a91721739e046fb6d Mon Sep 17 00:00:00 2001 From: David Terry Date: Tue, 3 Aug 2021 16:25:34 +0200 Subject: [PATCH 6/6] hevm: don't modify vm after vmError --- src/hevm/src/EVM.hs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/hevm/src/EVM.hs b/src/hevm/src/EVM.hs index a91e8c0fa..ec0968d53 100644 --- a/src/hevm/src/EVM.hs +++ b/src/hevm/src/EVM.hs @@ -2076,11 +2076,13 @@ cheatActions = CAbi vals -> case vals of [AbiAddress usr, AbiBytesDynamic (ConcreteBuffer -> newCode)] -> do vm <- get - -- TODO: make error messages nice here - when (usr == (view (state . codeContract) vm)) $ vmError (BadCheatCode sig) - assign (env . contracts . (ix usr) . contractcode) $ RuntimeCode newCode - assign (env . contracts . (ix usr) . codeOps) $ mkCodeOps newCode - assign (env . contracts . (ix usr) . opIxMap) $ mkOpIxMap newCode + if usr == (view (state . codeContract) vm) + then vmError (BadCheatCode sig) + else do + assign (env . contracts . (ix usr) . codeOps) $ mkCodeOps newCode + assign (env . contracts . (ix usr) . opIxMap) $ mkOpIxMap newCode + assign (env . contracts . (ix usr) . contractcode) $ RuntimeCode newCode + assign (cache . fetched . (ix usr) . contractcode) $ RuntimeCode newCode _ -> vmError (BadCheatCode sig) _ -> vmError (BadCheatCode sig),