diff --git a/flake.nix b/flake.nix index b98ea6c85..9225a5dfc 100644 --- a/flake.nix +++ b/flake.nix @@ -28,7 +28,8 @@ # TODO: maybe only override it for echidna-redistributable? pkgsStatic = if pkgs.stdenv.hostPlatform.isLinux then pkgs.pkgsStatic else pkgs; # this is not perfect for development as it hardcodes solc to 0.5.7, test suite runs fine though - solc = solc-pkgs.mkDefault pkgs pkgs.solc_0_5_7; + # 0.5.7 is not available on aarch64 darwin so alternatively pick 0.8.5 + solc = solc-pkgs.mkDefault pkgs (pkgs.solc_0_5_7 or pkgs.solc_0_8_5); secp256k1-static = pkgsStatic.secp256k1.overrideAttrs (attrs: { configureFlags = attrs.configureFlags ++ [ "--enable-static" ]; @@ -51,8 +52,8 @@ hevm = pkgs: pkgs.lib.pipe ((hsPkgs pkgs).callCabal2nix "hevm" (pkgs.fetchFromGitHub { owner = "ethereum"; repo = "hevm"; - rev = "c779777d18c8ff60867f009d434b44ce08188e01"; - sha256 = "sha256-JnJUZ9AxhxTP+TBMThksh0D4R6KFdzjgu1+fBeBERws="; + rev = "f1f45d3c0d9767a38df04f398d1eab8b66dbe7fc"; + sha256 = "sha256-3zEUwcZm4uZZLecvFTgVTV5CAm4qMfKPbLdwO88LnrY="; }) { secp256k1 = pkgs.secp256k1; }) ([ pkgs.haskell.lib.compose.dontCheck diff --git a/lib/Echidna/Exec.hs b/lib/Echidna/Exec.hs index 6800697d0..130e1b335 100644 --- a/lib/Echidna/Exec.hs +++ b/lib/Echidna/Exec.hs @@ -20,7 +20,8 @@ import Data.Maybe (fromMaybe, fromJust) import Data.Text qualified as T import Data.Vector qualified as V import Data.Vector.Unboxed.Mutable qualified as VMut -import System.Process (readProcessWithExitCode) +import System.Environment (lookupEnv, getEnvironment) +import System.Process qualified as P import EVM (bytecode, replaceCodeOfSelf, loadContract, exec1, vmOpIx, clearTStorages) import EVM.ABI @@ -178,13 +179,21 @@ execTxWith executeTx tx = do runFully -- resume execution -- Execute a FFI call - Just (PleaseDoFFI (cmd : args) continuation) -> do - (_, stdout, _) <- liftIO $ readProcessWithExitCode cmd args "" + Just (PleaseDoFFI (cmd : args) envs continuation) -> do + existingEnv <- liftIO getEnvironment + let mergedEnv = Map.toList $ Map.union envs $ Map.fromList existingEnv + let process = (P.proc cmd args) { P.env = Just mergedEnv } + (_, stdout, _) <- liftIO $ P.readCreateProcessWithExitCode process "" let encodedResponse = encodeAbiValue $ AbiTuple (V.fromList [AbiBytesDynamic . hexText . T.pack $ stdout]) fromEVM (continuation encodedResponse) runFully + Just (PleaseReadEnv var continuation) -> do + value <- liftIO $ lookupEnv var + fromEVM (continuation $ fromMaybe "" value) + runFully -- resume execution + -- No queries to answer, the tx is fully executed and the result is final _ -> pure vmResult diff --git a/lib/Echidna/SymExec.hs b/lib/Echidna/SymExec.hs index 69e61fc30..9aa5b6bc7 100644 --- a/lib/Echidna/SymExec.hs +++ b/lib/Echidna/SymExec.hs @@ -136,6 +136,7 @@ vmMakeSymbolic vm , forks = vm.forks , currentFork = vm.currentFork , labels = vm.labels + , osEnv = vm.osEnv } frameStateMakeSymbolic :: FrameState Concrete s -> FrameState Symbolic s diff --git a/lib/Echidna/Test.hs b/lib/Echidna/Test.hs index e11886f3d..c0fbb13db 100644 --- a/lib/Echidna/Test.hs +++ b/lib/Echidna/Test.hs @@ -271,12 +271,12 @@ checkAssertionEvent = any (T.isPrefixOf "AssertionFailed(") checkSelfDestructedTarget :: Addr -> DappInfo -> VM Concrete RealWorld -> TestValue checkSelfDestructedTarget addr _ vm = - let selfdestructs' = vm.tx.substate.selfdestructs + let selfdestructs' = vm.tx.subState.selfdestructs in BoolValue $ LitAddr addr `notElem` selfdestructs' checkAnySelfDestructed :: DappInfo -> VM Concrete RealWorld -> TestValue checkAnySelfDestructed _ vm = - BoolValue $ null vm.tx.substate.selfdestructs + BoolValue $ null vm.tx.subState.selfdestructs checkPanicEvent :: T.Text -> Events -> Bool checkPanicEvent n = any (T.isPrefixOf ("Panic(" <> n <> ")")) diff --git a/lib/Echidna/Transaction.hs b/lib/Echidna/Transaction.hs index 11669927e..22594bb8b 100644 --- a/lib/Echidna/Transaction.hs +++ b/lib/Echidna/Transaction.hs @@ -36,7 +36,7 @@ import Echidna.Types.World (World(..)) import Echidna.Types.Campaign hasSelfdestructed :: VM Concrete s -> Addr -> Bool -hasSelfdestructed vm addr = LitAddr addr `elem` vm.tx.substate.selfdestructs +hasSelfdestructed vm addr = LitAddr addr `elem` vm.tx.subState.selfdestructs -- | If half a tuple is zero, make both halves zero. Useful for generating -- delays, since block number only goes up with timestamp diff --git a/src/test/Tests/Cheat.hs b/src/test/Tests/Cheat.hs index 8a11b8367..9666a7d50 100644 --- a/src/test/Tests/Cheat.hs +++ b/src/test/Tests/Cheat.hs @@ -9,6 +9,10 @@ import Echidna.Types.Campaign (WorkerType(..)) cheatTests :: TestTree cheatTests = testGroup "Cheatcodes Tests" - [ testContract' "cheat/ffi.sol" (Just "TestFFI") (Just (> solcV (0,6,0))) (Just "cheat/ffi.yaml") False FuzzWorker + [ testContract' "cheat/ffi.sol" (Just "TestFFI") (Just (> solcV (0,5,0))) (Just "cheat/ffi.yaml") False FuzzWorker [ ("echidna_ffi passed", solved "echidna_ffi") ] + , testContract' "cheat/ffi2.sol" (Just "TestFFI") (Just (> solcV (0,5,0))) (Just "cheat/ffi.yaml") False FuzzWorker + [ ("echidna_ffi passed", solved "echidna_ffi") ] + , testContract' "cheat/gas.sol" (Just "TestCheatGas") (Just (> solcV (0,5,0))) (Just "cheat/ffi.yaml") False FuzzWorker + [ ("echidna_gas_zero passed", solved "echidna_gas_zero") ] ] diff --git a/stack.yaml b/stack.yaml index 15fa8406f..12c0d0699 100644 --- a/stack.yaml +++ b/stack.yaml @@ -5,7 +5,7 @@ packages: extra-deps: - git: https://github.com/ethereum/hevm.git - commit: c779777d18c8ff60867f009d434b44ce08188e01 + commit: f1f45d3c0d9767a38df04f398d1eab8b66dbe7fc - smt2-parser-0.1.0.1@sha256:1e1a4565915ed851c13d1e6b8bb5185cf5d454da3b43170825d53e221f753d77,1421 - spawn-0.3@sha256:b91e01d8f2b076841410ae284b32046f91471943dc799c1af77d666c72101f02,1162 diff --git a/tests/solidity/cheat/ffi2.sol b/tests/solidity/cheat/ffi2.sol new file mode 100644 index 000000000..a25313a7d --- /dev/null +++ b/tests/solidity/cheat/ffi2.sol @@ -0,0 +1,31 @@ +pragma experimental ABIEncoderV2; + +interface Hevm { + function setEnv(string calldata, string calldata) external; + function ffi(string[] calldata) external returns (bytes memory); +} + +contract TestFFI { + address constant HEVM_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; + + bytes32 hehe; + + function foo(int x) external { + // ABI encoded "gm", as a string + Hevm(HEVM_ADDRESS).setEnv("ECHIDNA_FOO_BAR", "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002676d000000000000000000000000000000000000000000000000000000000000"); + + string[] memory inputs = new string[](3); + inputs[0] = "sh"; + inputs[1] = "-c"; + inputs[2] = "printf '%s' \"$ECHIDNA_FOO_BAR\""; + + bytes memory res = Hevm(HEVM_ADDRESS).ffi(inputs); + + (string memory output) = abi.decode(res, (string)); + hehe = keccak256(bytes(output)); + } + + function echidna_ffi() public returns (bool){ + return hehe != keccak256("gm"); + } +} diff --git a/tests/solidity/cheat/gas.sol b/tests/solidity/cheat/gas.sol new file mode 100644 index 000000000..b55229914 --- /dev/null +++ b/tests/solidity/cheat/gas.sol @@ -0,0 +1,46 @@ +pragma experimental ABIEncoderV2; + +interface Hevm { + function assume(bool) external; +} + +contract C { + address public calledContract; + + constructor() public { + calledContract = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; + } + + function foo() public returns (uint256) { + assembly { + mstore(0x80, 0x4c63e562) + mstore(0xa0, 1) + + let addr := sload(0 /* calledContract.slot */) + let beforegas := gas() + let success := callcode(gas(), addr, 0, 0x9c, 0x24, 0, 0) + let aftergas := gas() + mstore(0x80, sub(beforegas, aftergas)) + return(0x80, 0x20) + } + } +} + +contract TestCheatGas { + uint256 spent = 123456; + C foo; + + constructor() public { + foo = new C(); + } + + function bar() public { + spent = foo.foo(); + } + + function echidna_gas_zero() public returns (bool){ + // 0x14 as measured from opcodes, but let's leave some leeway in case solc changes + // GAS PUSH1 0x0 DUP1 PUSH1 0x24 PUSH1 0x9C PUSH1 0x0 DUP7 GAS CALLCODE GAS + return spent > 0x20; + } +}