hevm 0.44.1 → 0.46.0
raw patch · 29 files changed
+704/−339 lines, 29 filesdep ~cryptonitedep ~sbvPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: cryptonite, sbv
API changes (from Hackage documentation)
- EVM: instance GHC.Classes.Eq EVM.Contract
- EVM: instance GHC.Classes.Eq EVM.ContractCode
- EVM.ABI: abiCalldata :: Text -> Vector AbiValue -> ByteString
- EVM.Emacs: prettifyCode :: ByteString -> String
+ EVM: [_accessedAddresses] :: SubState -> Set Addr
+ EVM: [_accessedStorageKeys] :: SubState -> Set (Addr, W256)
+ EVM: [vmoptTxAccessList] :: VMOpts -> Map Addr [W256]
+ EVM: accessAccountForGas :: Addr -> EVM Bool
+ EVM: accessAndBurn :: Addr -> EVM () -> EVM ()
+ EVM: accessStorageForGas :: Addr -> SymWord -> EVM Bool
+ EVM: accessedAddresses :: Lens' SubState (Set Addr)
+ EVM: accessedStorageKeys :: Lens' SubState (Set (Addr, W256))
+ EVM: concreteModexpGasFee :: ByteString -> Integer
+ EVM: ethsign :: PrivateKey -> Digest Keccak_256 -> Signature
+ EVM.ABI: CAbi :: [AbiValue] -> AbiVals
+ EVM.ABI: NoVals :: AbiVals
+ EVM.ABI: SAbi :: [SymWord] -> AbiVals
+ EVM.ABI: data AbiVals
+ EVM.ABI: decodeBuffer :: [AbiType] -> Buffer -> AbiVals
+ EVM.FeeSchedule: [g_access_list_address] :: FeeSchedule n -> n
+ EVM.FeeSchedule: [g_access_list_storage_key] :: FeeSchedule n -> n
+ EVM.FeeSchedule: [g_cold_account_access] :: FeeSchedule n -> n
+ EVM.FeeSchedule: [g_cold_sload] :: FeeSchedule n -> n
+ EVM.FeeSchedule: [g_warm_storage_read] :: FeeSchedule n -> n
+ EVM.FeeSchedule: berlin :: Num n => FeeSchedule n
+ EVM.FeeSchedule: eip2929 :: EIP n
+ EVM.RLP: octetsFull :: Int -> W256 -> ByteString
+ EVM.RLP: rlpAddrFull :: Addr -> RLP
+ EVM.RLP: rlpWordFull :: W256 -> RLP
+ EVM.Solidity: stripBytecodeMetadataSym :: [SWord 8] -> [SWord 8]
+ EVM.Symbolic: index :: Int -> Buffer -> SWord8
+ EVM.Transaction: AccessListEntry :: Addr -> [W256] -> AccessListEntry
+ EVM.Transaction: AccessListTransaction :: TxType
+ EVM.Transaction: LegacyTransaction :: TxType
+ EVM.Transaction: [accessAddress] :: AccessListEntry -> Addr
+ EVM.Transaction: [accessStorageKeys] :: AccessListEntry -> [W256]
+ EVM.Transaction: [txAccessList] :: Transaction -> [AccessListEntry]
+ EVM.Transaction: [txType] :: Transaction -> TxType
+ EVM.Transaction: accessListPrice :: FeeSchedule Integer -> [AccessListEntry] -> Integer
+ EVM.Transaction: data AccessListEntry
+ EVM.Transaction: data TxType
+ EVM.Transaction: instance Data.Aeson.Types.FromJSON.FromJSON EVM.Transaction.AccessListEntry
+ EVM.Transaction: instance GHC.Classes.Eq EVM.Transaction.TxType
+ EVM.Transaction: instance GHC.Show.Show EVM.Transaction.AccessListEntry
+ EVM.Transaction: instance GHC.Show.Show EVM.Transaction.TxType
+ EVM.Transaction: txAccessMap :: Transaction -> Map Addr [W256]
+ EVM.VMTest: data BlockchainCase
- EVM: FrameState :: Addr -> Addr -> ByteString -> Int -> [SymWord] -> Buffer -> Int -> (Buffer, SymWord) -> SymWord -> SAddr -> Word -> Buffer -> Bool -> FrameState
+ EVM: FrameState :: Addr -> Addr -> Buffer -> Int -> [SymWord] -> Buffer -> Int -> (Buffer, SymWord) -> SymWord -> SAddr -> Word -> Buffer -> Bool -> FrameState
- EVM: InitCode :: ByteString -> ContractCode
+ EVM: InitCode :: Buffer -> ContractCode
- EVM: RuntimeCode :: ByteString -> ContractCode
+ EVM: RuntimeCode :: Buffer -> ContractCode
- EVM: SubState :: [Addr] -> [Addr] -> [(Addr, Integer)] -> SubState
+ EVM: SubState :: [Addr] -> [Addr] -> Set Addr -> Set (Addr, W256) -> [(Addr, Integer)] -> SubState
- EVM: VMOpts :: Contract -> (Buffer, SymWord) -> SymWord -> Addr -> SAddr -> Addr -> W256 -> W256 -> W256 -> SymWord -> Addr -> W256 -> W256 -> W256 -> W256 -> FeeSchedule Integer -> W256 -> Bool -> StorageModel -> VMOpts
+ EVM: VMOpts :: Contract -> (Buffer, SymWord) -> SymWord -> Addr -> SAddr -> Addr -> W256 -> W256 -> W256 -> SymWord -> Addr -> W256 -> W256 -> W256 -> W256 -> FeeSchedule Integer -> W256 -> Bool -> StorageModel -> Map Addr [W256] -> VMOpts
- EVM: [_code] :: FrameState -> ByteString
+ EVM: [_code] :: FrameState -> Buffer
- EVM: bytecode :: Getter Contract ByteString
+ EVM: bytecode :: Getter Contract Buffer
- EVM: callChecks :: ?op :: Word8 => Contract -> Word -> Addr -> Word -> Word -> Word -> Word -> Word -> [SymWord] -> (Integer -> EVM ()) -> EVM ()
+ EVM: callChecks :: (?op :: Word8) => Contract -> Word -> Addr -> Addr -> Word -> Word -> Word -> Word -> Word -> [SymWord] -> (Integer -> EVM ()) -> EVM ()
- EVM: cheat :: ?op :: Word8 => (Word, Word) -> (Word, Word) -> EVM ()
+ EVM: cheat :: (?op :: Word8) => (Word, Word) -> (Word, Word) -> EVM ()
- EVM: code :: Lens' FrameState ByteString
+ EVM: code :: Lens' FrameState Buffer
- EVM: costOfCall :: FeeSchedule Integer -> Bool -> Word -> Word -> Word -> (Integer, Integer)
+ EVM: costOfCall :: FeeSchedule Integer -> Bool -> Word -> Word -> Word -> Addr -> EVM (Integer, Integer)
- EVM: create :: ?op :: Word8 => Addr -> Contract -> Word -> Word -> [SymWord] -> Addr -> ByteString -> EVM ()
+ EVM: create :: (?op :: Word8) => Addr -> Contract -> Word -> Word -> [SymWord] -> Addr -> Buffer -> EVM ()
- EVM: delegateCall :: ?op :: Word8 => Contract -> Word -> SAddr -> SAddr -> Word -> Word -> Word -> Word -> Word -> [SymWord] -> (Addr -> EVM ()) -> EVM ()
+ EVM: delegateCall :: (?op :: Word8) => Contract -> Word -> SAddr -> SAddr -> Word -> Word -> Word -> Word -> Word -> [SymWord] -> (Addr -> EVM ()) -> EVM ()
- EVM: executePrecompile :: ?op :: Word8 => Addr -> Integer -> Word -> Word -> Word -> Word -> [SymWord] -> EVM ()
+ EVM: executePrecompile :: (?op :: Word8) => Addr -> Integer -> Word -> Word -> Word -> Word -> [SymWord] -> EVM ()
- EVM: mkCodeOps :: ByteString -> Vector (Int, Op)
+ EVM: mkCodeOps :: Buffer -> Vector (Int, Op)
- EVM: mkOpIxMap :: ByteString -> Vector Int
+ EVM: mkOpIxMap :: Buffer -> Vector Int
- EVM: next :: ?op :: Word8 => EVM ()
+ EVM: next :: (?op :: Word8) => EVM ()
- EVM: precompiledContract :: ?op :: Word8 => Contract -> Word -> Addr -> Addr -> Word -> Word -> Word -> Word -> Word -> [SymWord] -> EVM ()
+ EVM: precompiledContract :: (?op :: Word8) => Contract -> Word -> Addr -> Addr -> Word -> Word -> Word -> Word -> Word -> [SymWord] -> EVM ()
- EVM: readOp :: Word8 -> ByteString -> Op
+ EVM: readOp :: Word8 -> Buffer -> Op
- EVM: stackOp1 :: ?op :: Word8 => (SymWord -> Integer) -> (SymWord -> SymWord) -> EVM ()
+ EVM: stackOp1 :: (?op :: Word8) => (SymWord -> Integer) -> (SymWord -> SymWord) -> EVM ()
- EVM: stackOp2 :: ?op :: Word8 => ((SymWord, SymWord) -> Integer) -> ((SymWord, SymWord) -> SymWord) -> EVM ()
+ EVM: stackOp2 :: (?op :: Word8) => ((SymWord, SymWord) -> Integer) -> ((SymWord, SymWord) -> SymWord) -> EVM ()
- EVM: stackOp3 :: ?op :: Word8 => ((SymWord, SymWord, SymWord) -> Integer) -> ((SymWord, SymWord, SymWord) -> SymWord) -> EVM ()
+ EVM: stackOp3 :: (?op :: Word8) => ((SymWord, SymWord, SymWord) -> Integer) -> ((SymWord, SymWord, SymWord) -> SymWord) -> EVM ()
- EVM.ABI: decodeStaticArgs :: Buffer -> [SWord 256]
+ EVM.ABI: decodeStaticArgs :: Buffer -> [SymWord]
- EVM.Emacs: codeByHash :: W256 -> VM -> Maybe ByteString
+ EVM.Emacs: codeByHash :: W256 -> VM -> Maybe Buffer
- EVM.FeeSchedule: FeeSchedule :: n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> FeeSchedule n
+ EVM.FeeSchedule: FeeSchedule :: n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> n -> FeeSchedule n
- EVM.Format: prettyvmresult :: ?context :: DappContext => VMResult -> String
+ EVM.Format: prettyvmresult :: (?context :: DappContext) => VMResult -> String
- EVM.Format: showAbiValue :: ?context :: DappContext => AbiValue -> Text
+ EVM.Format: showAbiValue :: (?context :: DappContext) => AbiValue -> Text
- EVM.Format: showAbiValues :: ?context :: DappContext => Vector AbiValue -> Text
+ EVM.Format: showAbiValues :: (?context :: DappContext) => Vector AbiValue -> Text
- EVM.Format: showCall :: ?context :: DappContext => [AbiType] -> Buffer -> Text
+ EVM.Format: showCall :: (?context :: DappContext) => [AbiType] -> Buffer -> Text
- EVM.Format: showError :: ?context :: DappContext => ByteString -> Text
+ EVM.Format: showError :: (?context :: DappContext) => ByteString -> Text
- EVM.Format: showValue :: ?context :: DappContext => AbiType -> Buffer -> Text
+ EVM.Format: showValue :: (?context :: DappContext) => AbiType -> Buffer -> Text
- EVM.Format: showValues :: ?context :: DappContext => [AbiType] -> Buffer -> Text
+ EVM.Format: showValues :: (?context :: DappContext) => [AbiType] -> Buffer -> Text
- EVM.Format: textAbiValues :: ?context :: DappContext => Vector AbiValue -> [Text]
+ EVM.Format: textAbiValues :: (?context :: DappContext) => Vector AbiValue -> [Text]
- EVM.Format: textValues :: ?context :: DappContext => [AbiType] -> Buffer -> [Text]
+ EVM.Format: textValues :: (?context :: DappContext) => [AbiType] -> Buffer -> [Text]
- EVM.Op: OpPush :: !W256 -> Op
+ EVM.Op: OpPush :: !SymWord -> Op
- EVM.Transaction: Transaction :: ByteString -> W256 -> W256 -> W256 -> W256 -> W256 -> Maybe Addr -> W256 -> W256 -> Transaction
+ EVM.Transaction: Transaction :: ByteString -> W256 -> W256 -> W256 -> W256 -> W256 -> Maybe Addr -> W256 -> W256 -> TxType -> [AccessListEntry] -> Transaction
- EVM.UnitTest: formatTestLog :: ?context :: DappContext => Map W256 Event -> Log -> Maybe Text
+ EVM.UnitTest: formatTestLog :: (?context :: DappContext) => Map W256 Event -> Log -> Maybe Text
- EVM.UnitTest: formatTestLogs :: ?context :: DappContext => Map W256 Event -> Seq Log -> Text
+ EVM.UnitTest: formatTestLogs :: (?context :: DappContext) => Map W256 Event -> Seq Log -> Text
- EVM.UnitTest: prettyCalldata :: ?context :: DappContext => (Buffer, SymWord) -> Text -> [AbiType] -> Query Text
+ EVM.UnitTest: prettyCalldata :: (?context :: DappContext) => (Buffer, SymWord) -> Text -> [AbiType] -> Query Text
- EVM.VMTest: checkExpectation :: Bool -> Case -> VM -> IO Bool
+ EVM.VMTest: checkExpectation :: HasCallStack => Bool -> Case -> VM -> IO Bool
Files
- CHANGELOG.md +33/−1
- hevm-cli/hevm-cli.hs +11/−8
- hevm.cabal +3/−3
- run-blockchain-tests +22/−8
- src/EVM.hs +305/−146
- src/EVM/ABI.hs +39/−53
- src/EVM/Concrete.hs +1/−2
- src/EVM/Dapp.hs +0/−2
- src/EVM/Debug.hs +2/−2
- src/EVM/Dev.hs +10/−4
- src/EVM/Emacs.hs +1/−8
- src/EVM/Exec.hs +3/−2
- src/EVM/Facts.hs +17/−8
- src/EVM/FeeSchedule.hs +25/−0
- src/EVM/Fetch.hs +3/−3
- src/EVM/Flatten.hs +25/−7
- src/EVM/Format.hs +0/−1
- src/EVM/Op.hs +2/−2
- src/EVM/Patricia.hs +0/−1
- src/EVM/RLP.hs +10/−0
- src/EVM/Solidity.hs +21/−2
- src/EVM/SymExec.hs +9/−5
- src/EVM/Symbolic.hs +6/−2
- src/EVM/TTY.hs +9/−12
- src/EVM/Transaction.hs +75/−18
- src/EVM/Types.hs +1/−8
- src/EVM/UnitTest.hs +3/−4
- src/EVM/VMTest.hs +24/−12
- test/test.hs +44/−15
CHANGELOG.md view
@@ -1,10 +1,43 @@ # hevm changelog +## 0.46.0 - 2021-04-29++### Added++- Updated to Berlin! Conformant with GeneralStateTests at commit hash `644967e345bbc6642fab613e1b1737abbe131f78`.++### Fixed++- ADDMOD and MULMOD by zero yields zero.+- Address calculation for newly created contracts.+- Accomodated for the notorious "anomolies on the main network" (see yellow paper Appendix K for trivia)+- A hevm crash when debugging a SELFDESTRUCT contract.++## 0.45.0 - 2021-03-22++### Added++- Two new cheatcodes were added: `sign(uint sk, bytes message)` and `addr(uint sk)`. Taken together+ these should allow for much more ergonomic testing of code that handles signed messages.+- Symbolic execution can deal with partially symbolic bytecode, allowing for symbolic constructor arguments to be given in tests.++### Fixed++- Fixed a bug in the abiencoding.+- Fixed the range being generated by ints.+- `hevm flatten` combines the SPDX license identifiers of all source files.++### Changed++- updated `nixpkgs` to the `20.09` channel+- Arbitrary instance of AbiType can no longer generate a tuple+ ## 0.44.1 - 2020-02-02 ### Changed - hevm cheatcodes now accept symbolic arguments, allowing e.g. symbolic jumps in time in unit tests+- More efficient arithmetic overflow checks by translating queries to a more [intelligent form](www.microsoft.com/en-us/research/wp-content/uploads/2016/02/z3prefix.pdf). ## 0.44.0 - 2020-01-26 @@ -14,7 +47,6 @@ well as `--combined-json`. - addresses in the trace output are prefixed with `ContractName@0x...` if there is a corresponding contract and `@0x...` otherwise.-- More efficient arithmetic overflow checks by translating queries to a more [intelligent form](www.microsoft.com/en-us/research/wp-content/uploads/2016/02/z3prefix.pdf). ### Fixed
hevm-cli/hevm-cli.hs view
@@ -47,6 +47,7 @@ import qualified EVM.UnitTest import GHC.IO.Encoding+import GHC.Stack import Control.Concurrent.Async (async, waitCatch) import Control.Lens hiding (pre, passing) import Control.Monad (void, when, forM_, unless)@@ -54,14 +55,14 @@ import Data.ByteString (ByteString) import Data.List (intercalate, isSuffixOf) import Data.Tree-import Data.Text (Text, unpack, pack)+import Data.Text (unpack, pack) 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, stdout, stderr, utf8)+import System.IO (hFlush, stdout, stderr) import System.Directory (withCurrentDirectory, listDirectory) import System.Exit (exitFailure, exitWith, ExitCode(..)) import System.Environment (setEnv)@@ -731,7 +732,7 @@ caller' = addr caller 0 origin' = addr origin 0 calldata' = ConcreteBuffer $ bytes calldata ""- codeType = if create cmd then EVM.InitCode else EVM.RuntimeCode+ codeType = (if create cmd then EVM.InitCode else EVM.RuntimeCode) . ConcreteBuffer address' = if create cmd then addr address (createAddress origin' (word nonce 0)) else addr address 0xacab@@ -752,10 +753,11 @@ , EVM.vmoptGasprice = word gasprice 0 , EVM.vmoptMaxCodeSize = word maxcodesize 0xffffffff , EVM.vmoptDifficulty = word difficulty diff- , EVM.vmoptSchedule = FeeSchedule.istanbul+ , EVM.vmoptSchedule = FeeSchedule.berlin , EVM.vmoptChainId = word chainid 1 , EVM.vmoptCreate = create cmd , EVM.vmoptStorageModel = ConcreteS+ , EVM.vmoptTxAccessList = mempty -- TODO: support me soon } word f def = fromMaybe def (f cmd) addr f def = fromMaybe def (f cmd)@@ -837,7 +839,7 @@ 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+ codeType = (if create cmd then EVM.InitCode else EVM.RuntimeCode) . ConcreteBuffer address' = if create cmd then addr address (createAddress origin' (word nonce 0)) else addr address 0xacab@@ -857,15 +859,16 @@ , EVM.vmoptGasprice = word gasprice 0 , EVM.vmoptMaxCodeSize = word maxcodesize 0xffffffff , EVM.vmoptDifficulty = word difficulty diff- , EVM.vmoptSchedule = FeeSchedule.istanbul+ , EVM.vmoptSchedule = FeeSchedule.berlin , EVM.vmoptChainId = word chainid 1 , EVM.vmoptCreate = create cmd , EVM.vmoptStorageModel = fromMaybe SymbolicS (storageModel cmd)+ , EVM.vmoptTxAccessList = mempty } word f def = fromMaybe def (f cmd) addr f def = fromMaybe def (f cmd) -launchTest :: Command Options.Unwrapped -> IO ()+launchTest :: HasCallStack => Command Options.Unwrapped -> IO () launchTest cmd = do #if MIN_VERSION_aeson(1, 0, 0) parsed <- VMTest.parseBCSuite <$> LazyByteString.readFile (file cmd)@@ -885,7 +888,7 @@ #endif #if MIN_VERSION_aeson(1, 0, 0)-runVMTest :: Bool -> Mode -> Maybe Int -> (String, VMTest.Case) -> IO Bool+runVMTest :: HasCallStack => Bool -> Mode -> Maybe Int -> (String, VMTest.Case) -> IO Bool runVMTest diffmode mode timelimit (name, x) = do let vm0 = VMTest.vmForCase x
hevm.cabal view
@@ -2,7 +2,7 @@ name: hevm version:- 0.44.1+ 0.46.0 synopsis: Ethereum virtual machine evaluator description:@@ -104,7 +104,7 @@ filepath >= 1.4.2 && < 1.5, vty >= 5.25.1 && < 5.31, cereal >= 0.5.8 && < 0.6,- cryptonite >= 0.25 && < 0.28,+ cryptonite >= 0.27 && < 0.29, memory >= 0.14.18 && < 0.16, data-dword >= 0.3.1 && < 0.4, fgl >= 5.7.0 && < 5.8,@@ -121,7 +121,7 @@ restless-git >= 0.7 && < 0.8, rosezipper >= 0.2 && < 0.3, s-cargot >= 0.1.4 && < 0.2,- sbv >= 8.7.5,+ sbv >= 8.9, semver-range >= 0.2.7 && < 0.3, temporary >= 1.3 && < 1.4, text-format >= 0.3.2 && < 0.4,
run-blockchain-tests view
@@ -46,7 +46,7 @@ <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)+(Test suite: <span class=GeneralStateTests</span>GeneralStateTests</span> for Berlin) </header> <h2>Failed tests</h2> <table id=failed>@@ -82,14 +82,28 @@ { 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"+ if [ -d $x ]; then+ for y in $x/*; do+ if [[ $y =~ .*$match.* ]] && [[ -n $skip && $y =~ .*$skip.* ]]; then+ for job in $(<$y jq '.|keys[]' -r); do+ echo -n "$job " ; echo "skip"+ done+ elif [[ $y =~ .*$match.* ]]; then+ set +e+ "$HEVM" bc-test --file $y --timeout $timeout 2>&1+ set -e+ fi done- elif [[ $x =~ .*$match.* ]]; then- set +e- "$HEVM" bc-test --file $x --timeout $timeout 2>&1- set -e+ else+ 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 fi done } | {
src/EVM.hs view
@@ -20,28 +20,27 @@ import EVM.ABI import EVM.Types import EVM.Solidity-import EVM.Concrete (createAddress, wordValue, keccakBlob, create2Address)+import EVM.Concrete (createAddress, wordValue, keccakBlob, create2Address, readMemoryWord) import EVM.Symbolic import EVM.Op import EVM.FeeSchedule (FeeSchedule (..)) import Options.Generic as Options import qualified EVM.Precompiled -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.Set (Set, insert, member, fromList) 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 Data.List (find) import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LS@@ -57,7 +56,10 @@ import Crypto.Number.ModArithmetic (expFast) import qualified Crypto.Hash as Crypto-import Crypto.Hash (Digest, SHA256, RIPEMD160)+import Crypto.Hash (Digest, SHA256, RIPEMD160, digestFromByteString)+import Crypto.PubKey.ECC.ECDSA (signDigestWith, PrivateKey(..), Signature(..))+import Crypto.PubKey.ECC.Types (getCurveByName, CurveName(..), Point(..))+import Crypto.PubKey.ECC.Generate (generateQ) -- * Data types @@ -200,6 +202,7 @@ , vmoptChainId :: W256 , vmoptCreate :: Bool , vmoptStorageModel :: StorageModel+ , vmoptTxAccessList :: Map Addr [W256] } deriving Show -- | A log entry@@ -238,7 +241,7 @@ data FrameState = FrameState { _contract :: Addr , _codeContract :: Addr- , _code :: ByteString+ , _code :: Buffer , _pc :: Int , _stack :: [SymWord] , _memory :: Buffer@@ -254,14 +257,14 @@ -- | 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+ { _gasprice :: Word+ , _txgaslimit :: Word+ , _origin :: Addr+ , _toAddr :: Addr+ , _value :: SymWord+ , _substate :: SubState+ , _isCreate :: Bool+ , _txReversion :: Map Addr Contract } deriving (Show) @@ -269,6 +272,8 @@ data SubState = SubState { _selfdestructs :: [Addr] , _touchedAccounts :: [Addr]+ , _accessedAddresses :: Set Addr+ , _accessedStorageKeys :: Set (Addr, W256) , _refunds :: [(Addr, Integer)] -- in principle we should include logs here, but do not for now }@@ -279,9 +284,9 @@ -- 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)+ = InitCode Buffer -- ^ "Constructor" code, during contract creation+ | RuntimeCode Buffer -- ^ "Instance" code, after contract creation+ deriving (Show) -- | A contract can either have concrete or symbolic storage -- depending on what type of execution we are doing@@ -313,7 +318,6 @@ } 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@@ -385,9 +389,9 @@ -- | An "external" view of a contract's bytecode, appropriate for -- e.g. @EXTCODEHASH@.-bytecode :: Getter Contract ByteString+bytecode :: Getter Contract Buffer bytecode = contractcode . to f- where f (InitCode _) = BS.empty+ where f (InitCode _) = mempty f (RuntimeCode b) = b instance Semigroup Cache where@@ -420,16 +424,25 @@ -- * Data constructors makeVm :: VMOpts -> VM-makeVm o = VM+makeVm o = + let txaccessList = vmoptTxAccessList o+ txorigin = vmoptOrigin o+ txtoAddr = vmoptAddress o+ initialAccessedAddrs = fromList $ [txorigin, txtoAddr] ++ [1..9] ++ (Map.keys txaccessList)+ initialAccessedStorageKeys = fromList $ foldMap (uncurry (map . (,))) (Map.toList txaccessList)+ touched = if vmoptCreate o then [txorigin] else [txorigin, txtoAddr]+ in + VM { _result = Nothing , _frames = mempty , _tx = TxState { _gasprice = w256 $ vmoptGasprice o , _txgaslimit = w256 $ vmoptGaslimit o- , _origin = vmoptOrigin o- , _toAddr = vmoptAddress o+ , _origin = txorigin+ , _toAddr = txtoAddr , _value = vmoptValue o- , _substate = SubState mempty mempty mempty+ , _substate = SubState mempty touched initialAccessedAddrs initialAccessedStorageKeys mempty+ --, _accessList = txaccessList , _isCreate = vmoptCreate o , _txReversion = Map.fromList [(vmoptAddress o, vmoptContract o)]@@ -481,8 +494,10 @@ initialContract theContractCode = Contract { _contractcode = theContractCode , _codehash =- if BS.null theCode then 0 else- keccak (stripBytecodeMetadata theCode)+ case theCode of+ ConcreteBuffer b -> keccak (stripBytecodeMetadata b)+ SymbolicBuffer _ -> 0+ , _storage = Concrete mempty , _balance = 0 , _nonce = if creation then 1 else 0@@ -552,23 +567,24 @@ _ -> underrun - else if the state pc >= num (BS.length (the state code))+ else if the state pc >= len (the state code) then doStop else do- let ?op = BS.index (the state code) (the state pc)+ let ?op = fromMaybe (error "could not analyze symbolic code") $ unliteral $ EVM.Symbolic.index (the state pc) (the state code) case ?op of -- op: PUSH x | x >= 0x60 && x <= 0x7f -> do let !n = num x - 0x60 + 1- !xs = padRight n $ BS.take n (BS.drop (1 + the state pc)- (the state code))+ !xs = case the state code of+ ConcreteBuffer b -> w256lit $ word $ padRight n $ BS.take n (BS.drop (1 + the state pc) b)+ SymbolicBuffer b -> readSWord' 0 $ padLeft' 32 $ take n $ drop (1 + the state pc) b limitStack 1 $ burn g_verylow $ do next- push (w256 (word xs))+ pushSym xs -- op: DUP x | x >= 0x80 && x <= 0x8f -> do@@ -730,7 +746,7 @@ 0x31 -> case stk of (x':xs) -> forceConcrete x' $ \x ->- burn g_balance $+ accessAndBurn (num x) $ fetchAccount (num x) $ \c -> do next assign (state . stack) xs@@ -784,7 +800,7 @@ -- op: CODESIZE 0x38 -> limitStack 1 . burn g_base $- next >> push (num (BS.length (the state code)))+ next >> push (num (len (the state code))) -- op: CODECOPY 0x39 ->@@ -794,7 +810,7 @@ accessUnboundedMemoryRange fees memOffset n $ do next assign (state . stack) xs- copyBytesToMemory (ConcreteBuffer (the state code))+ copyBytesToMemory (the state code) n codeOffset memOffset _ -> underrun @@ -813,11 +829,11 @@ assign (state . stack) xs push (w256 1) else- burn g_extcode $+ accessAndBurn (num x) $ fetchAccount (num x) $ \c -> do next assign (state . stack) xs- push (num (BS.length (view bytecode c)))+ push (num (len (view bytecode c))) [] -> underrun @@ -830,13 +846,15 @@ : codeSize' : xs ) -> forceConcrete4 (extAccount', memOffset', codeOffset', codeSize') $- \(extAccount, memOffset, codeOffset, codeSize) ->- burn (g_extcode + g_copy * ceilDiv (num codeSize) 32) $+ \(extAccount, memOffset, codeOffset, codeSize) -> do+ acc <- accessAccountForGas (num extAccount)+ let cost = if acc then g_warm_storage_read else g_cold_account_access+ burn (cost + 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))+ copyBytesToMemory (view bytecode c) codeSize codeOffset memOffset _ -> underrun @@ -854,7 +872,7 @@ accessUnboundedMemoryRange fees xTo xSize $ do next assign (state . stack) xs- if len (the state returndata) < num xFrom + num xSize+ if num (len (the state returndata)) < xFrom + xSize || xFrom + xSize < xFrom then vmError InvalidMemoryAccess else copyBytesToMemory (the state returndata) xSize xFrom xTo _ -> underrun@@ -863,13 +881,15 @@ 0x3f -> case stk of (x':xs) -> forceConcrete x' $ \x ->- burn g_extcodehash $ do+ accessAndBurn (num x) $ 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)))+ else case view bytecode c of+ ConcreteBuffer b -> push (num (keccak b))+ b'@(SymbolicBuffer b) -> pushSym (S (FromKeccak b') $ symkeccak' b) [] -> underrun @@ -962,8 +982,10 @@ -- op: SLOAD 0x54 -> case stk of- (x:xs) ->- burn g_sload $+ (x:xs) -> do+ acc <- accessStorageForGas self x+ let cost = if acc then g_warm_storage_read else g_cold_sload+ burn cost $ accessStorage self x $ \y -> do next assign (state . stack) (y:xs)@@ -983,7 +1005,7 @@ 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+ storage_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@@ -994,7 +1016,9 @@ -- assume worst case scenario _ -> g_sset - burn cost $ do+ acc <- accessStorageForGas self x+ let cold_storage_cost = if acc then 0 else g_cold_sload+ burn (storage_cost + cold_storage_cost) $ do next assign (state . stack) xs modifying (env . contracts . ix self . storage)@@ -1088,8 +1112,10 @@ 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 (num gas') xValue xs newAddr initCode+ _ <- accessAccountForGas newAddr+ burn (cost - gas') $+ let initCode = readMemory (num xOffset) (num xSize) vm+ in create self this (num gas') xValue xs newAddr initCode _ -> underrun -- op: CALL@@ -1203,12 +1229,14 @@ \(xValue, xOffset, xSize, xSalt) -> accessMemoryRange fees xOffset xSize $ do availableGas <- use (state . gas)- forceConcreteBuffer (readMemory (num xOffset) (num xSize) vm) $ \initCode ->++ forceConcreteBuffer (readMemory (num xOffset) (num xSize) vm) $ \initCode -> do let newAddr = create2Address self (num xSalt) initCode (cost, gas') = costOfCreate fees availableGas xSize- in burn (cost - gas') $- create self this (num gas') xValue xs newAddr initCode+ _ <- accessAccountForGas newAddr+ burn (cost - gas') $+ create self this (num gas') xValue xs newAddr (ConcreteBuffer initCode) _ -> underrun -- op: STATICCALL@@ -1239,14 +1267,15 @@ 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+ (xTo':_) -> forceConcrete xTo' $ \(num -> xTo) -> do+ acc <- accessAccountForGas (num xTo)+ let cost = if acc then 0 else g_cold_account_access+ funds = view balance this+ recipientExists = accountExists xTo vm+ c_new = if not recipientExists && funds /= 0+ then num g_selfdestruct_newaccount+ else 0+ burn (g_selfdestruct + c_new + cost) $ do destructs <- use (tx . substate . selfdestructs) unless (elem self destructs) $ refund r_selfdestruct selfdestruct self@@ -1280,18 +1309,18 @@ -- | Checks a *CALL for failure; OOG, too many callframes, memory access etc. callChecks :: (?op :: Word8)- => Contract -> Word -> Addr -> Word -> Word -> Word -> Word -> Word -> [SymWord]+ => Contract -> Word -> Addr -> Addr -> Word -> Word -> Word -> Word -> Word -> [SymWord] -- continuation with gas available for call -> (Integer -> EVM ()) -> EVM ()-callChecks this xGas xContext xValue xInOffset xInSize xOutOffset xOutSize xs continue = do+callChecks this xGas xContext xTo 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+ (cost, gas') <- costOfCall fees recipientExists xValue availableGas xGas xTo burn (cost - gas') $ do if xValue > view balance this then do@@ -1318,7 +1347,7 @@ -> [SymWord] -> EVM () precompiledContract this xGas precompileAddr recipient xValue inOffset inSize outOffset outSize xs =- callChecks this xGas recipient xValue inOffset inSize outOffset outSize xs $ \gas' ->+ callChecks this xGas recipient precompileAddr xValue inOffset inSize outOffset outSize xs $ \gas' -> do executePrecompile precompileAddr gas' inOffset inSize outOffset outSize xs self <- use (state . contract)@@ -1333,7 +1362,6 @@ transfer self recipient xValue touchAccount self touchAccount recipient- touchAccount precompileAddr _ -> vmError UnexpectedSymbolicArg _ -> underrun @@ -1418,16 +1446,15 @@ (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))+ if isZero (96 + lenb + lene) lenm input'+ then truncpadlit (num lenm) (asBE (0 :: Int))+ else+ 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@@ -1511,6 +1538,7 @@ lenm = w256 $ word $ LS.toStrict $ lazySlice 64 96 input in (lenb, lene, lenm) +--- checks if a range of ByteString bs starting at offset and length size is all zeros. isZero :: Word -> Word -> ByteString -> Bool isZero offset size bs = LS.all (== 0) $@@ -1660,7 +1688,9 @@ -- EIP 161 accountEmpty :: Contract -> Bool accountEmpty c =- (view contractcode c == RuntimeCode mempty)+ case view contractcode c of+ RuntimeCode b -> len b == 0+ _ -> False && (view nonce c == 0) && (view balance c == 0) @@ -1669,7 +1699,7 @@ finalize = do let revertContracts = use (tx . txReversion) >>= assign (env . contracts)- revertSubstate = assign (tx . substate) (SubState mempty mempty mempty)+ revertSubstate = assign (tx . substate) (SubState mempty mempty mempty mempty mempty) use result >>= \case Nothing ->@@ -1688,7 +1718,7 @@ createe <- use (state . contract) createeExists <- (Map.member createe) <$> use (env . contracts) - when (creation && createeExists) $ forceConcreteBuffer output $ \code' -> replaceCode createe (RuntimeCode code')+ when (creation && createeExists) $ replaceCode createe (RuntimeCode output) -- compute and pay the refund to the caller and the -- corresponding payment to the miner@@ -1845,6 +1875,35 @@ selfdestruct :: Addr -> EVM() selfdestruct = pushTo ((tx . substate) . selfdestructs) +accessAndBurn :: Addr -> EVM () -> EVM ()+accessAndBurn x cont = do+ FeeSchedule {..} <- use ( block . schedule )+ acc <- accessAccountForGas x+ let cost = if acc then g_warm_storage_read else g_cold_account_access+ burn cost cont++-- | returns a wrapped boolean- if true, this address has been touched before in the txn (warm gas cost as in EIP 2929)+-- otherwise cold+accessAccountForGas :: Addr -> EVM Bool+accessAccountForGas addr = do+ accessedAddrs <- use (tx . substate . accessedAddresses)+ let accessed = member addr accessedAddrs+ assign (tx . substate . accessedAddresses) (insert addr accessedAddrs)+ return accessed++-- | returns a wrapped boolean- if true, this slot has been touched before in the txn (warm gas cost as in EIP 2929)+-- otherwise cold+accessStorageForGas :: Addr -> SymWord -> EVM Bool+accessStorageForGas addr key = do+ accessedStrkeys <- use (tx . substate . accessedStorageKeys)+ case maybeLitWord key of+ Just litword -> do+ let litword256 = wordValue litword+ let accessed = member (addr, litword256) accessedStrkeys+ assign (tx . substate . accessedStorageKeys) (insert (addr, litword256) accessedStrkeys)+ return accessed+ _ -> return False+ -- * Cheat codes -- The cheat code is 7109709ecfa91a80626ff3989d68f67f5b1dd12d.@@ -1882,36 +1941,87 @@ Map.fromList [ action "warp(uint256)" $ \sig _ _ input -> case decodeStaticArgs input of- [x] -> assign (block . timestamp) (mksym x)+ [x] -> assign (block . timestamp) x _ -> vmError (BadCheatCode sig), action "roll(uint256)" $ \sig _ _ input -> case decodeStaticArgs input of- [x] -> forceConcrete (mksym x) (assign (block . number))+ [x] -> forceConcrete x (assign (block . number)) _ -> vmError (BadCheatCode sig), action "store(address,bytes32,bytes32)" $ \sig _ _ input -> case decodeStaticArgs input of [a, slot, new] ->- makeUnique (mksym $ sFromIntegral a) $ \(C _ (num -> a')) ->+ makeUnique a $ \(C _ (num -> a')) -> fetchAccount a' $ \_ -> do- modifying (env . contracts . ix a' . storage) (writeStorage (mksym slot) (mksym new))+ modifying (env . contracts . ix a' . storage) (writeStorage slot new) _ -> vmError (BadCheatCode sig), action "load(address,bytes32)" $ \sig outOffset _ input -> case decodeStaticArgs input of [a, slot] ->- makeUnique (mksym $ sFromIntegral a) $ \(C _ (num -> a'))->- accessStorage a' (mksym slot) $ \res -> do+ makeUnique a $ \(C _ (num -> a'))->+ accessStorage a' slot $ \res -> do assign (state . returndata . word256At 0) res assign (state . memory . word256At outOffset) res+ _ -> vmError (BadCheatCode sig),++ action "sign(uint256,bytes32)" $+ \sig outOffset _ input -> case decodeStaticArgs input of+ [sk, hash] ->+ forceConcrete sk $ \sk' ->+ forceConcrete hash $ \(C _ hash') -> let+ curve = getCurveByName SEC_p256k1+ priv = PrivateKey curve (num sk')+ digest = digestFromByteString (word256Bytes hash')+ in do+ case digest of+ Nothing -> vmError (BadCheatCode sig)+ Just digest' -> do+ let s = ethsign priv digest'+ v = if (sign_s s) % 2 == 0 then 27 else 28+ encoded = encodeAbiValue $+ AbiTuple (RegularVector.fromList+ [ AbiUInt 8 v+ , AbiBytes 32 (word256Bytes . fromInteger $ sign_r s)+ , AbiBytes 32 (word256Bytes . fromInteger $ sign_s s)+ ])+ assign (state . returndata) (ConcreteBuffer encoded)+ copyBytesToMemory (ConcreteBuffer encoded) (num . BS.length $ encoded) 0 outOffset+ _ -> vmError (BadCheatCode sig),++ action "addr(uint256)" $+ \sig outOffset _ input -> case decodeStaticArgs input of+ [sk] -> forceConcrete sk $ \sk' -> let+ curve = getCurveByName SEC_p256k1+ pubPoint = generateQ curve (num sk')+ encodeInt = encodeAbiValue . AbiUInt 256 . fromInteger+ in do+ case pubPoint of+ PointO -> do vmError (BadCheatCode sig)+ Point x y -> do+ -- See yellow paper #286+ let+ pub = BS.concat [ encodeInt x, encodeInt y ]+ addr = w256lit . num . word256 . BS.drop 12 . BS.take 32 . keccakBytes $ pub+ assign (state . returndata . word256At 0) addr+ assign (state . memory . word256At outOffset) addr _ -> vmError (BadCheatCode sig)+ ] where action s f = (abiKeccak s, f (Just $ abiKeccak s))- mksym x = S (Todo "abidecode" []) x +-- | Hack deterministic signing, totally insecure...+ethsign :: PrivateKey -> Digest Crypto.Keccak_256 -> Signature+ethsign sk digest = go 420+ where+ go k = case signDigestWith k sk digest of+ Nothing -> go (k + 1)+ Just sig -> sig+ -- * General call implementation ("delegateCall")+-- note that the continuation is ignored in the precompile case delegateCall :: (?op :: Word8) => Contract -> Word -> SAddr -> SAddr -> Word -> Word -> Word -> Word -> Word -> [SymWord]@@ -1927,7 +2037,7 @@ assign (state . stack) xs cheat (xInOffset, xInSize) (xOutOffset, xOutSize) else- callChecks this gasGiven xContext' xValue xInOffset xInSize xOutOffset xOutSize xs $+ callChecks this gasGiven xContext' xTo' xValue xInOffset xInSize xOutOffset xOutSize xs $ \xGas -> do vm0 <- get fetchAccount xTo' . const $@@ -1980,12 +2090,14 @@ -- EIP 684 collision :: Maybe Contract -> Bool collision c' = case c' of- Just c -> (view contractcode c /= RuntimeCode mempty) || (view nonce c /= 0)+ Just c -> (view nonce c /= 0) || case view contractcode c of+ RuntimeCode b -> len b /= 0+ _ -> True Nothing -> False create :: (?op :: Word8) => Addr -> Contract- -> Word -> Word -> [SymWord] -> Addr -> ByteString -> EVM ()+ -> Word -> Word -> [SymWord] -> Addr -> Buffer -> EVM () create self this xGas' xValue xs newAddr initCode = do vm0 <- get let xGas = num xGas'@@ -2151,9 +2263,19 @@ -- Were we calling? CallContext _ _ (num -> outOffset) (num -> outSize) _ _ _ reversion substate' -> do + -- Excerpt K.1. from the yellow paper:+ -- K.1. Deletion of an Account Despite Out-of-gas.+ -- At block 2675119, in the transaction 0xcf416c536ec1a19ed1fb89e4ec7ffb3cf73aa413b3aa9b77d60e4fd81a4296ba,+ -- an account at address 0x03 was called and an out-of-gas occurred during the call.+ -- Against the equation (197), this added 0x03 in the set of touched addresses, and this transaction turned σ[0x03] into ∅.++ -- In other words, we special case address 0x03 and keep it in the set of touched accounts during revert+ touched <- use (tx . substate . touchedAccounts)+ let+ substate'' = over touchedAccounts (maybe id cons (find ((==) 3) touched)) substate' revertContracts = assign (env . contracts) reversion- revertSubstate = assign (tx . substate) substate'+ revertSubstate = assign (tx . substate) substate'' case how of -- Case 1: Returning from a call?@@ -2191,9 +2313,8 @@ case how of -- Case 4: Returning during a creation?- FrameReturned output ->- forceConcreteBuffer output $ \output' -> do- replaceCode createe (RuntimeCode output')+ FrameReturned output -> do+ replaceCode createe (RuntimeCode output) assign (state . returndata) mempty reclaimRemainingGasAllowance push (num createe)@@ -2273,7 +2394,7 @@ => Word -> (SymWord -> f (SymWord)) -> Buffer -> f Buffer word256At i = lens getter setter where- getter = readMemoryWord i+ getter = EVM.Symbolic.readMemoryWord i setter m x = setMemoryWord i x m -- * Tracing@@ -2385,7 +2506,7 @@ self <- use (state . codeContract) theCodeOps <- use (env . contracts . ix self . codeOps) theOpIxMap <- use (env . contracts . ix self . opIxMap)- if x < num (BS.length theCode) && BS.index theCode (num x) == 0x5b+ 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@@ -2402,14 +2523,22 @@ -- 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 ->+mkOpIxMap :: Buffer -> Vector Int+mkOpIxMap xs = Vector.create $ Vector.new (len 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+ case xs of+ ConcreteBuffer xs' ->+ let (_, _, _, m) =+ BS.foldl' (go v) (0 :: Word8, 0, 0, return ()) xs'+ in m >> return v+ SymbolicBuffer xs' ->+ let (_, _, _, m) =+ foldl (go' v) (0, 0, 0, return ()) (stripBytecodeMetadataSym xs')+ in m >> return v+ where+ -- concrete case 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) _ =@@ -2419,14 +2548,34 @@ go v (n, !i, !j, !m) _ = {- PUSH data. -} (n - 1, i + 1, j, m >> Vector.write v i j) + -- symbolic case+ go' v (0, !i, !j, !m) x = case unliteral x of+ Just x' -> if x' >= 0x60 && x' <= 0x7f+ -- start of PUSH op --+ then (x' - 0x60 + 1, i + 1, j, m >> Vector.write v i j)+ -- other data --+ else (0, i + 1, j + 1, m >> Vector.write v i j)+ _ -> error "cannot analyze symbolic code"++ {- 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 (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+ code' = vm ^. state . code+ xs = case code' of+ ConcreteBuffer xs' -> ConcreteBuffer (BS.drop i xs')+ SymbolicBuffer xs' -> SymbolicBuffer (drop i xs')+ op = case xs of+ ConcreteBuffer b -> BS.index b 0+ SymbolicBuffer b -> fromSized $ fromMaybe (error "unexpected symbolic code") (unliteral (b !! 0))+ in if (len code' < i) then Nothing- else Just (readOp op (BS.drop 1 xs))+ else Just (readOp op xs) vmOpIx :: VM -> Maybe Int vmOpIx vm =@@ -2461,14 +2610,16 @@ then Map.fromList (zip xs (vm ^. state . stack)) else mempty -readOp :: Word8 -> ByteString -> Op+readOp :: Word8 -> Buffer -> 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')+ xs'' = case xs of+ ConcreteBuffer xs' -> num $ EVM.Concrete.readMemoryWord 0 $ BS.take (num n) xs'+ SymbolicBuffer xs' -> readSWord' 0 $ take (num n) xs'+ in OpPush xs'' readOp x _ = case x of 0x00 -> OpStop 0x01 -> OpAdd@@ -2544,8 +2695,8 @@ 0xff -> OpSelfdestruct _ -> OpUnknown x -mkCodeOps :: ByteString -> RegularVector.Vector (Int, Op)-mkCodeOps bytes = RegularVector.fromList . toList $ go 0 bytes+mkCodeOps :: Buffer -> RegularVector.Vector (Int, Op)+mkCodeOps (ConcreteBuffer bytes) = RegularVector.fromList . toList $ go 0 bytes where go !i !xs = case BS.uncons xs of@@ -2553,34 +2704,40 @@ mempty Just (x, xs') -> let j = opSize x- in (i, readOp x xs') Seq.<| go (i + j) (BS.drop j xs)+ in (i, readOp x (ConcreteBuffer xs')) Seq.<| go (i + j) (BS.drop j xs)+mkCodeOps (SymbolicBuffer bytes) = RegularVector.fromList . toList $ go' 0 (stripBytecodeMetadataSym bytes)+ where+ go' !i !xs =+ case uncons xs of+ Nothing ->+ mempty+ Just (x, xs') ->+ let x' = fromSized $ fromMaybe (error "unexpected symbolic code argument") $ unliteral x+ j = opSize x'+ in (i, readOp x' (SymbolicBuffer xs')) Seq.<| go' (i + j) (drop j xs) -- * Gas cost calculation helpers -- Gas cost function for CALL, transliterated from the Yellow Paper. costOfCall :: FeeSchedule Integer- -> Bool -> Word -> Word -> Word- -> (Integer, Integer)-costOfCall (FeeSchedule {..}) recipientExists xValue availableGas' xGas' =- (c_gascap + c_extra, c_callgas)- where- availableGas = num availableGas'- xGas = num xGas'- 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+ -> Bool -> Word -> Word -> Word -> Addr+ -> EVM (Integer, Integer)+costOfCall (FeeSchedule {..}) recipientExists xValue availableGas' xGas' target = do+ acc <- accessAccountForGas target+ let call_base_gas = if acc then g_warm_storage_read else g_cold_account_access+ availableGas = num availableGas'+ xGas = num xGas'+ c_new = if not recipientExists && xValue /= 0+ then num g_newaccount+ else 0+ c_xfer = if xValue /= 0 then num g_callvalue else 0+ c_extra = num call_base_gas + c_xfer + c_new+ c_gascap = if availableGas >= c_extra+ then min xGas (allButOne64th (availableGas - c_extra))+ else xGas+ c_callgas = if xValue /= 0 then c_gascap + num g_callstipend else c_gascap+ return (c_gascap + c_extra, c_callgas) -- Gas cost of create, including hash cost if needed costOfCreate@@ -2594,6 +2751,22 @@ hashCost = g_sha3word * ceilDiv (num hashSize) 32 initGas = allButOne64th (availableGas - createCost) +concreteModexpGasFee :: ByteString -> Integer+concreteModexpGasFee input = max 200 ((multiplicationComplexity * iterCount) `div` 3)+ where (lenb, lene, lenm) = parseModexpLength input+ ez = isZero (96 + lenb) lene input+ e' = w256 $ word $ LS.toStrict $+ lazySlice (96 + lenb) (min 32 lene) input+ nwords :: Integer+ nwords = ceilDiv (num $ max lenb lenm) 8+ multiplicationComplexity = nwords * nwords+ iterCount' :: Integer+ iterCount' | lene <= 32 && ez = 0+ | lene <= 32 = num (log2 e')+ | e' == 0 = 8 * (num lene - 32)+ | otherwise = num (log2 e') + 8 * (num lene - 32)+ iterCount = max iterCount' 1+ -- Gas cost of precompiles costOfPrecompile :: FeeSchedule Integer -> Addr -> Buffer -> Integer costOfPrecompile (FeeSchedule {..}) precompileAddr input =@@ -2607,24 +2780,10 @@ -- 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)+ 0x5 -> concreteModexpGasFee input' 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+ SymbolicBuffer _ -> error "unsupported: symbolic MODEXP gas cost calc"+ ConcreteBuffer b -> b -- ECADD 0x6 -> g_ecadd -- ECMUL
src/EVM/ABI.hs view
@@ -32,6 +32,7 @@ ( AbiValue (..) , AbiType (..) , AbiKind (..)+ , AbiVals (..) , abiKind , Event (..) , Anonymity (..)@@ -42,12 +43,12 @@ , genAbiValue , abiValueType , abiTypeSolidity- , abiCalldata , abiMethod , emptyAbi , encodeAbiValue , decodeAbiValue , decodeStaticArgs+ , decodeBuffer , formatString , parseTypeName , makeAbiValue@@ -64,14 +65,12 @@ import Data.ByteString (ByteString) import Data.DoubleWord (Word256, Int256, signedWord) import Data.Functor (($>))-import Data.Monoid ((<>)) import Data.Text (Text, pack, unpack) import Data.Text.Encoding (encodeUtf8, decodeUtf8') import Data.Vector (Vector, toList) import Data.Word (Word32) import Data.List (intercalate)-import Data.SBV (SWord, fromBytes, sFromIntegral, literal)-import Data.Maybe+import Data.SBV (SWord, fromBytes) import GHC.Generics import Test.QuickCheck hiding ((.&.), label)@@ -276,22 +275,6 @@ Static -> pure () Dynamic -> putAbi x -abiValueSize :: AbiValue -> Int-abiValueSize x =- case x of- 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 + roundTo32Bytes (BS.length xs)- AbiArrayDynamic _ xs -> 32 + Vector.sum (Vector.map abiHeadSize xs) +- Vector.sum (Vector.map abiTailSize xs)- AbiString s -> 32 + roundTo32Bytes (BS.length s)- AbiTuple v -> sum (abiValueSize <$> v)- abiTailSize :: AbiValue -> Int abiTailSize x = case abiKind (abiValueType x) of@@ -300,13 +283,10 @@ case x of 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)+ AbiArrayDynamic _ xs -> 32 + sum ((abiHeadSize <$> xs) <> (abiTailSize <$> xs))+ AbiArray _ _ xs -> sum ((abiHeadSize <$> xs) <> (abiTailSize <$> xs))+ AbiTuple v -> sum ((abiHeadSize <$> v) <> (abiTailSize <$> v)) _ -> error "impossible"- where headSize y = if abiKind (abiValueType y) == Static- then abiValueSize y- else 32 abiHeadSize :: AbiValue -> Int abiHeadSize x =@@ -319,25 +299,23 @@ AbiBytes n _ -> roundTo32Bytes n AbiAddress _ -> 32 AbiBool _ -> 32- AbiArray _ _ xs -> Vector.sum (Vector.map abiHeadSize xs) +- Vector.sum (Vector.map abiTailSize xs)- AbiBytesDynamic _ -> 32- AbiArrayDynamic _ _ -> 32- AbiString _ -> 32- AbiTuple v -> sum (abiHeadSize <$> v) +- sum (abiTailSize <$> v)+ AbiTuple v -> sum (abiHeadSize <$> v)+ AbiArray _ _ xs -> sum (abiHeadSize <$> xs)+ _ -> error "impossible" putAbiSeq :: Vector AbiValue -> Put putAbiSeq xs =- do snd $ Vector.foldl' f (headSize, pure ()) (Vector.zip xs tailSizes)- Vector.sequence_ (Vector.map putAbiTail xs)+ do putHeads headSize $ toList xs+ Vector.sequence_ (putAbiTail <$> xs) where headSize = Vector.sum $ Vector.map abiHeadSize xs- tailSizes = Vector.map abiTailSize xs- f (i, m) (x, j) =+ putHeads _ [] = pure ()+ putHeads offset (x:xs') = case abiKind (abiValueType x) of- Static -> (i, m >> putAbi x)- Dynamic -> (i + j, m >> putAbi (AbiUInt 256 (fromIntegral i)))+ Static -> do putAbi x+ putHeads offset xs'+ Dynamic -> do putAbi (AbiUInt 256 (fromIntegral offset))+ putHeads (offset + abiTailSize x) xs' encodeAbiValue :: AbiValue -> BS.ByteString encodeAbiValue = BSLazy.toStrict . runPut . putAbi@@ -353,11 +331,6 @@ 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 :: Vector AbiType -> Text -> Maybe AbiType parseTypeName = P.parseMaybe . typeWithArraySuffix @@ -429,7 +402,7 @@ AbiIntType n -> do a <- genUInt n let AbiUInt _ x = a- pure $ AbiInt n (signedWord x)+ pure $ AbiInt n (signedWord (x - 2^(n-1))) AbiAddressType -> (\(AbiUInt _ x) -> AbiAddress (fromIntegral x)) <$> genUInt 20 AbiBoolType ->@@ -453,22 +426,19 @@ genUInt n = AbiUInt n <$> arbitraryIntegralWithMax (2^n-1) instance Arbitrary AbiType where- arbitrary = sized $ \n -> oneof $ -- prevent empty tuples+ arbitrary = oneof $ -- doesn't create any tuples [ (AbiUIntType . (* 8)) <$> choose (1, 32) , (AbiIntType . (* 8)) <$> choose (1, 32) , pure AbiAddressType , pure AbiBoolType- , AbiBytesType . getPositive <$> arbitrary+ , AbiBytesType <$> choose (1,32) , pure AbiBytesDynamicType , pure AbiStringType , AbiArrayDynamicType <$> scale (`div` 2) arbitrary , AbiArrayType <$> (getPositive <$> arbitrary) <*> scale (`div` 2) arbitrary- ] <>- [AbiTupleType- <$> scale (`div` 2) (Vector.fromList <$> arbitrary)- | n /= 0]+ ] instance Arbitrary AbiValue where arbitrary = arbitrary >>= genAbiValue@@ -540,12 +510,28 @@ skipSpaces return a) `sepBy` (char ',')) -decodeStaticArgs :: Buffer -> [SWord 256]+data AbiVals = NoVals | CAbi [AbiValue] | SAbi [SymWord]++decodeBuffer :: [AbiType] -> Buffer -> AbiVals+decodeBuffer tps (ConcreteBuffer b)+ = case runGetOrFail (getAbiSeq (length tps) tps) (BSLazy.fromStrict b) of+ Right ("", _, args) -> CAbi . toList $ args+ _ -> NoVals+decodeBuffer tps b@(SymbolicBuffer _)+ = if containsDynamic tps+ then NoVals+ else SAbi . decodeStaticArgs $ b+ where+ isDynamic t = abiKind t == Dynamic+ containsDynamic = or . fmap isDynamic++decodeStaticArgs :: Buffer -> [SymWord] decodeStaticArgs buffer = let bs = case buffer of ConcreteBuffer b -> litBytes b SymbolicBuffer b -> b- in fmap (\i -> fromBytes $ take 32 (drop (i*32) bs)) [0..((length bs) `div` 32 - 1)]+ in fmap (\i -> S (FromBytes buffer) $+ fromBytes $ take 32 (drop (i*32) bs)) [0..((length bs) `div` 32 - 1)] -- A modification of 'arbitrarySizedBoundedIntegral' quickcheck library -- which takes the maxbound explicitly rather than relying on a Bounded instance.
src/EVM/Concrete.hs view
@@ -12,7 +12,6 @@ import Data.Bits (Bits (..), shiftL, shiftR) import Data.ByteString (ByteString) import Data.Maybe (fromMaybe)-import Data.Semigroup ((<>)) import Data.Word (Word8) import qualified Data.ByteString as BS@@ -106,7 +105,7 @@ | otherwise = g (x * x) ((y - 1) `shiftR` 1) (x * z) createAddress :: Addr -> W256 -> Addr-createAddress a n = num $ keccak $ rlpList [rlpWord160 a, rlpWord256 n]+createAddress a n = num $ keccak $ rlpList [rlpAddrFull a, rlpWord256 n] create2Address :: Addr -> W256 -> ByteString -> Addr create2Address a s b = num $ keccak $ mconcat
src/EVM/Dapp.hs view
@@ -18,11 +18,9 @@ import Data.Text (Text, isPrefixOf, pack, unpack) import Data.Text.Encoding (encodeUtf8) import Data.Map (Map, toList)-import Data.Monoid ((<>)) import Data.Maybe (isJust, fromJust) import Data.Word (Word32) -import Control.Applicative ((<$>)) import Control.Arrow ((>>>)) import Control.Lens
src/EVM/Debug.hs view
@@ -3,6 +3,7 @@ import EVM (Contract, storage, nonce, balance, bytecode, codehash) import EVM.Solidity (SrcMap, srcMapFile, srcMapOffset, srcMapLength, SourceCache, sourceFiles) import EVM.Types (Addr)+import EVM.Symbolic (len) import Control.Arrow (second) import Control.Lens@@ -28,11 +29,10 @@ prettyContract :: Contract -> Doc prettyContract c = object- [ (text "codesize", int (ByteString.length (c ^. bytecode)))+ [ (text "codesize", int (len (c ^. bytecode))) , (text "codehash", text (show (c ^. codehash))) , (text "balance", int (fromIntegral (c ^. balance))) , (text "nonce", int (fromIntegral (c ^. nonce)))- , (text "code", text (show (ByteString.take 16 (c ^. bytecode)))) , (text "storage", text (show (c ^. storage))) ]
src/EVM/Dev.hs view
@@ -24,6 +24,7 @@ import qualified Data.Aeson as JSON import Options.Generic import Data.SBV.Trans.Control+import Data.Maybe (fromMaybe) import Control.Monad.State.Strict (execStateT) import qualified Data.Map as Map@@ -156,10 +157,15 @@ getOp :: VM -> Word8 getOp vm =- if BS.length (view (state . code) vm) <= view (state . EVM.pc) vm- then 0- else fromIntegral $ BS.index (view (state . code) vm) (view (state . EVM.pc) vm)-+ let i = vm ^. state . EVM.pc+ code' = vm ^. state . code+ xs = case code' of+ ConcreteBuffer xs' -> ConcreteBuffer (BS.drop i xs')+ SymbolicBuffer xs' -> SymbolicBuffer (drop i xs')+ in if len xs == 0 then 0+ else case xs of+ ConcreteBuffer b -> BS.index b 0+ SymbolicBuffer b -> fromSized $ fromMaybe (error "unexpected symbolic code") (unliteral (b !! 0)) vmtrace :: VM -> VMTrace vmtrace vm =
src/EVM/Emacs.hs view
@@ -25,7 +25,6 @@ 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)@@ -38,7 +37,6 @@ 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 @@ -301,7 +299,7 @@ currentFileName == wantedFileName && currentLineNumber == wantedLineNumber -codeByHash :: W256 -> VM -> Maybe ByteString+codeByHash :: W256 -> VM -> Maybe Buffer codeByHash h vm = do let cs = view (env . contracts) vm c <- List.find (\c -> h == (view codehash c)) (Map.elems cs)@@ -311,9 +309,6 @@ 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@@ -324,7 +319,6 @@ output $ L [ A "step" , L [A ("vm" :: Text), sexp (view uiVm s)]- , L [A ("newCodes" :: Text), sexp ((fmap prettifyCode) <$> sendCodes)] ] fromMaybe noMap $ do dapp <- view uiVmDapp s@@ -339,7 +333,6 @@ , A (txt (srcMapLength sm)) , A (txt (srcMapJump sm)) ]- , L [A ("newCodes" :: Text), sexp ((fmap prettifyCode) <$> sendCodes)] ]
src/EVM/Exec.hs view
@@ -21,7 +21,7 @@ vmForEthrunCreation :: ByteString -> VM vmForEthrunCreation creationCode = (makeVm $ VMOpts- { vmoptContract = initialContract (InitCode creationCode)+ { vmoptContract = initialContract (InitCode (ConcreteBuffer creationCode)) , vmoptCalldata = (mempty, 0) , vmoptValue = 0 , vmoptAddress = createAddress ethrunAddress 1@@ -36,10 +36,11 @@ , vmoptGas = 0xffffffffffffffff , vmoptGaslimit = 0xffffffffffffffff , vmoptMaxCodeSize = 0xffffffff- , vmoptSchedule = FeeSchedule.istanbul+ , vmoptSchedule = FeeSchedule.berlin , vmoptChainId = 1 , vmoptCreate = False , vmoptStorageModel = ConcreteS+ , vmoptTxAccessList = mempty }) & set (env . contracts . at ethrunAddress) (Just (initialContract (RuntimeCode mempty)))
src/EVM/Facts.hs view
@@ -38,7 +38,7 @@ import EVM (VM, Contract, Cache) import EVM.Symbolic (litWord, forceLit) import EVM (balance, nonce, storage, bytecode, env, contracts, contract, state, cache, fetched)-import EVM.Types (Addr, Word, SymWord)+import EVM.Types (Addr, Word, SymWord, Buffer(..)) import qualified EVM @@ -112,12 +112,21 @@ _ -> Nothing contractFacts :: Addr -> Contract -> [Fact]-contractFacts a x = storageFacts a x ++- [ BalanceFact a (view balance x)- , NonceFact a (view nonce x)- , CodeFact a (view bytecode x)- ]+contractFacts a x = case view bytecode x of+ ConcreteBuffer b -> + storageFacts a x +++ [ BalanceFact a (view balance x)+ , NonceFact a (view nonce x)+ , CodeFact a b+ ]+ SymbolicBuffer b ->+ -- here simply ignore storing the bytecode+ storageFacts a x +++ [ BalanceFact a (view balance x)+ , NonceFact a (view nonce x)+ ] + storageFacts :: Addr -> Contract -> [Fact] storageFacts a x = case view storage x of EVM.Symbolic _ _ -> []@@ -151,7 +160,7 @@ apply1 vm fact = case fact of CodeFact {..} -> flip execState vm $ do- assign (env . contracts . at addr) (Just (EVM.initialContract (EVM.RuntimeCode blob)))+ assign (env . contracts . at addr) (Just (EVM.initialContract (EVM.RuntimeCode (ConcreteBuffer blob)))) when (view (state . contract) vm == addr) $ EVM.loadContract addr StorageFact {..} -> vm & over (env . contracts . ix addr . storage) (EVM.writeStorage (litWord which) (litWord what))@@ -164,7 +173,7 @@ apply2 vm fact = case fact of CodeFact {..} -> flip execState vm $ do- assign (cache . fetched . at addr) (Just (EVM.initialContract (EVM.RuntimeCode blob)))+ assign (cache . fetched . at addr) (Just (EVM.initialContract (EVM.RuntimeCode (ConcreteBuffer blob)))) when (view (state . contract) vm == addr) $ EVM.loadContract addr StorageFact {..} -> vm & over (cache . fetched . ix addr . storage) (EVM.writeStorage (litWord which) (litWord what))
src/EVM/FeeSchedule.hs view
@@ -45,6 +45,11 @@ , g_pairing_base :: n , g_fround :: n , r_block :: n+ , g_cold_sload :: n+ , g_cold_account_access :: n+ , g_warm_storage_read :: n+ , g_access_list_address :: n+ , g_access_list_storage_key :: n } deriving Show -- For the purposes of this module, we define an EIP as just a fee@@ -115,6 +120,11 @@ , g_pairing_base = 100000 , g_fround = 1 , r_block = 2000000000000000000+ , g_cold_sload = 2100+ , g_cold_account_access = 2600+ , g_warm_storage_read = 100+ , g_access_list_address = 2400+ , g_access_list_storage_key = 1900 } metropolis :: Num n => FeeSchedule n@@ -158,3 +168,18 @@ istanbul :: Num n => FeeSchedule n istanbul = eip1108 . eip1884 . eip2028 . eip2200 $ metropolis++ -- EIP2929: Gas cost increases for state access opcodes+ -- <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2929.md>+eip2929 :: EIP n+eip2929 fees = fees+ { g_sload = 100 + , g_sreset = 5000 - 2100 + , g_call = 2600+ , g_balance = 2600+ , g_extcode = 2600+ , g_extcodehash = 2600+ }++berlin :: Num n => FeeSchedule n+berlin = eip2929 istanbul
src/EVM/Fetch.hs view
@@ -6,7 +6,7 @@ import Prelude hiding (Word) -import EVM.Types (Addr, w256, W256, hexText, Word)+import EVM.Types (Addr, w256, W256, hexText, Word, Buffer(..)) import EVM.Symbolic (litWord) import EVM (IsUnique(..), EVM, Contract, Block, initialContract, nonce, balance, external) import qualified EVM.FeeSchedule as FeeSchedule@@ -104,7 +104,7 @@ number <- readText <$> j ^? key "number" . _String difficulty <- readText <$> j ^? key "difficulty" . _String -- default codesize, default gas limit, default feescedule- return $ EVM.Block coinbase timestamp number difficulty 0xffffffff 0xffffffff FeeSchedule.istanbul+ return $ EVM.Block coinbase timestamp number difficulty 0xffffffff 0xffffffff FeeSchedule.berlin fetchWithSession :: Text -> Session -> Value -> IO (Maybe Value) fetchWithSession url sess x = do@@ -123,7 +123,7 @@ theBalance <- MaybeT $ fetch (QueryBalance addr) return $- initialContract (EVM.RuntimeCode theCode)+ initialContract (EVM.RuntimeCode (ConcreteBuffer theCode)) & set nonce (w256 theNonce) & set balance (w256 theBalance) & set external True
src/EVM/Flatten.hs view
@@ -10,7 +10,7 @@ -- This module is mostly independent from the rest of Hevm, -- using only the source code metadata support modules. -import EVM.Dapp (DappInfo, dappSources)+import EVM.Dapp (DappInfo, dappSources, regexMatches) import EVM.Solidity (sourceAsts) import EVM.Demand (demand) @@ -35,12 +35,11 @@ import Control.Monad (forM) import Data.ByteString (ByteString) import Data.Foldable (foldl', toList)-import Data.List (sort, nub)+import Data.List (sort, nub, (\\)) import Data.Map (Map, (!), (!?)) import Data.Maybe (mapMaybe, isJust, catMaybes, fromMaybe)-import Data.Monoid ((<>)) import Data.Text (Text, unpack, pack, intercalate)-import Data.Text.Encoding (encodeUtf8)+import Data.Text.Encoding (encodeUtf8, decodeUtf8) import Text.Read (readMaybe) import qualified Data.Map as Map@@ -179,6 +178,8 @@ pragma :: Text pragma = maximalPragma (Map.elems (Map.filterWithKey (\k _ -> k `elem` ordered) asts)) + license :: Text+ license = joinLicenses (Map.elems (Map.filterWithKey (\k _ -> k `elem` ordered) asts)) -- Read the source files in order and strip unwanted directives. -- Also add an informative comment with the original source file path. sources <-@@ -191,8 +192,9 @@ (prefixContractAst contractsAndStructsToRename contractStructs- (stripImportsAndPragmas (src, 0) (asts ! path))- (asts ! path)), "\n"+ (stripImportsAndPragmas (stripLicense src) (asts ! path))+ (asts ! path))+ , "\n" ] -- Force all evaluation before any printing happens, to avoid@@ -201,15 +203,22 @@ -- Finally print the whole concatenation. putStrLn $ "// hevm: flattened sources of " <> unpack target+ putStrLn (unpack license) putStrLn (unpack pragma) BS.putStr (mconcat sources) +joinLicenses :: [Value] -> Text+joinLicenses asts =+ case nub $ mapMaybe (\ast -> getAttribute "license" ast >>= preview _String) asts of+ [] -> ""+ x -> "// SPDX-License-Identifier: " <> intercalate " AND " x+ -- | Construct a new Solidity version pragma for the highest mentioned version -- given a list of source file ASTs. maximalPragma :: [Value] -> Text maximalPragma asts = ( case mapMaybe versions asts of- [] -> error "no Solidity version pragmas in any source files"+ [] -> "" -- allow for no pragma xs -> "pragma solidity " <> pack (show (rangeIntersection xs))@@ -283,6 +292,15 @@ Just t == preview (key "name" . _String) x || Just t == preview (key "nodeType" . _String) x +-- | Removes all lines containing "SPDX-License-Identifier"+stripLicense :: ByteString -> (ByteString, Int)+stripLicense bs =+ (encodeUtf8 $ Text.unlines (lines' \\ licenseLines), - sum (((1 +) . Text.length) <$> licenseLines))+ where lines' = Text.lines $ decodeUtf8 bs+ licenseLines = filter (regexMatches "SPDX-License-Identifier") lines'++-- | (bytes, offset) where offset is added or incremeneted as text is+-- inserted or removed from the source file stripImportsAndPragmas :: (ByteString, Int) -> Value -> (ByteString, Int) stripImportsAndPragmas bso ast = stripAstNodes bso ast p where
src/EVM/Format.hs view
@@ -30,7 +30,6 @@ import Data.DoubleWord (signedWord) import Data.Foldable (toList) 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')
src/EVM/Op.hs view
@@ -3,7 +3,7 @@ , opString ) where -import EVM.Types (W256)+import EVM.Types (SymWord) import Data.Word (Word8) import Numeric (showHex) @@ -83,7 +83,7 @@ | OpDup !Word8 | OpSwap !Word8 | OpLog !Word8- | OpPush !W256+ | OpPush !SymWord | OpUnknown Word8 deriving (Show, Eq)
src/EVM/Patricia.hs view
@@ -12,7 +12,6 @@ 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
src/EVM/RLP.hs view
@@ -66,6 +66,10 @@ octets x = BS.pack $ dropWhile (== 0) [fromIntegral (shiftR x (8 * i)) | i <- reverse [0..31]] +octetsFull :: Int -> W256 -> ByteString+octetsFull n x =+ BS.pack $ [fromIntegral (shiftR x (8 * i)) | i <- reverse [0..n]]+ octets160 :: Addr -> ByteString octets160 x = BS.pack $ dropWhile (== 0) [fromIntegral (shiftR x (8 * i)) | i <- reverse [0..19]]@@ -73,6 +77,12 @@ rlpWord256 :: W256 -> RLP rlpWord256 0 = BS mempty rlpWord256 n = BS $ octets n++rlpWordFull :: W256 -> RLP+rlpWordFull = BS . octetsFull 31++rlpAddrFull :: Addr -> RLP+rlpAddrFull = BS . octetsFull 19 . num rlpWord160 :: Addr -> RLP rlpWord160 0 = BS mempty
src/EVM/Solidity.hs view
@@ -1,4 +1,5 @@ {-# Language DeriveAnyClass #-}+{-# Language DataKinds #-} {-# Language StrictData #-} {-# Language TemplateHaskell #-} {-# Language OverloadedStrings #-}@@ -40,6 +41,7 @@ , sourceLines , sourceAsts , stripBytecodeMetadata+ , stripBytecodeMetadataSym , signature , solc , Language(..)@@ -52,6 +54,7 @@ import EVM.ABI import EVM.Types+import Data.SBV import Control.Applicative import Control.Monad@@ -87,7 +90,7 @@ import qualified Data.Map.Strict as Map import qualified Data.Text as Text import qualified Data.Vector as Vector-import Data.List (sort)+import Data.List (sort, isPrefixOf, isInfixOf, elemIndex, tails, findIndex) data StorageItem = StorageItem { _type :: SlotType,@@ -510,7 +513,7 @@ stdjson :: Language -> Text -> Text stdjson lang src = decodeUtf8 $ toStrict $ encode $ StandardJSON lang src --- When doing CREATE and passing constructor arguments, Solidity loads+-- | When doing CREATE and passing constructor arguments, Solidity loads -- the argument data via the creation bytecode, since there is no "calldata" -- for CREATE. --@@ -528,6 +531,22 @@ case find ((/= mempty) . snd) stripCandidates of Nothing -> bs Just (b, _) -> b++stripBytecodeMetadataSym :: [SWord 8] -> [SWord 8]+stripBytecodeMetadataSym b =+ let+ concretes :: [Maybe Word8]+ concretes = (fmap fromSized) <$> unliteral <$> b+ bzzrs :: [[Maybe Word8]]+ bzzrs = fmap (Just) <$> BS.unpack <$> knownBzzrPrefixes+ candidates = (flip Data.List.isInfixOf concretes) <$> bzzrs+ in case elemIndex True candidates of+ Nothing -> b+ Just i -> let Just ind = infixIndex (bzzrs !! i) concretes+ in take ind b++infixIndex :: (Eq a) => [a] -> [a] -> Maybe Int+infixIndex needle haystack = findIndex (isPrefixOf needle) (tails haystack) knownBzzrPrefixes :: [ByteString] knownBzzrPrefixes = [
src/EVM/SymExec.hs view
@@ -116,11 +116,11 @@ ConcreteS -> return $ Concrete mempty c <- SAddr <$> freshVar_ value' <- var "CALLVALUE" <$> freshVar_- return $ loadSymVM (RuntimeCode x) symstore storagemodel c value' (SymbolicBuffer cd', cdlen) & over constraints ((<>) [cdconstraint])+ return $ loadSymVM (RuntimeCode (ConcreteBuffer x)) symstore storagemodel c value' (SymbolicBuffer cd', cdlen) & over constraints ((<>) [cdconstraint]) loadSymVM :: ContractCode -> Storage -> StorageModel -> SAddr -> SymWord -> (Buffer, SymWord) -> VM loadSymVM x initStore model addr callvalue' calldata' =- (makeVm $ VMOpts+ (makeVm $ VMOpts { vmoptContract = contractWithStore x initStore , vmoptCalldata = calldata' , vmoptValue = callvalue'@@ -136,10 +136,11 @@ , vmoptGas = 0xffffffffffffffff , vmoptGaslimit = 0xffffffffffffffff , vmoptMaxCodeSize = 0xffffffff- , vmoptSchedule = FeeSchedule.istanbul+ , vmoptSchedule = FeeSchedule.berlin , vmoptChainId = 1 , vmoptCreate = False , vmoptStorageModel = model+ , vmoptTxAccessList = mempty }) & set (env . contracts . at (createAddress ethrunAddress 1)) (Just (contractWithStore x initStore)) @@ -343,7 +344,10 @@ -- | 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 + bytecodeA' = if BS.null bytecodeA then BS.pack [0] else bytecodeA+ bytecodeB' = if BS.null bytecodeB then BS.pack [0] else bytecodeB+ preStateA <- abstractVM signature' [] bytecodeA' SymbolicS let preself = preStateA ^. state . contract precaller = preStateA ^. state . caller@@ -351,7 +355,7 @@ prestorage = preStateA ^?! env . contracts . ix preself . storage (calldata', cdlen) = view (state . calldata) preStateA pathconds = view constraints preStateA- preStateB = loadSymVM (RuntimeCode bytecodeB) prestorage SymbolicS precaller callvalue' (calldata', cdlen) & set constraints pathconds+ preStateB = loadSymVM (RuntimeCode (ConcreteBuffer bytecodeB')) prestorage SymbolicS precaller callvalue' (calldata', cdlen) & set constraints pathconds smtState <- queryState push 1
src/EVM/Symbolic.hs view
@@ -63,12 +63,12 @@ addmod :: SymWord -> SymWord -> SymWord -> SymWord addmod (S a x) (S b y) (S c z) = let to512 :: SWord 256 -> SWord 512 to512 = sFromIntegral- in S (Todo "addmod" [a, b, c]) $ sFromIntegral $ ((to512 x) + (to512 y)) `sMod` (to512 z)+ in S (Todo "addmod" [a, b, c]) $ ite (z .== 0) 0 $ sFromIntegral $ ((to512 x) + (to512 y)) `sMod` (to512 z) mulmod :: SymWord -> SymWord -> SymWord -> SymWord mulmod (S a x) (S b y) (S c z) = let to512 :: SWord 256 -> SWord 512 to512 = sFromIntegral- in S (Todo "mulmod" [a, b, c]) $ sFromIntegral $ ((to512 x) * (to512 y)) `sMod` (to512 z)+ in S (Todo "mulmod" [a, b, c]) $ ite (z .== 0) 0 $ sFromIntegral $ ((to512 x) * (to512 y)) `sMod` (to512 z) -- | Signed less than slt :: SymWord -> SymWord -> SymWord@@ -202,6 +202,10 @@ readSWord :: Word -> Buffer -> SymWord readSWord i (SymbolicBuffer x) = readSWord' i x readSWord i (ConcreteBuffer x) = num $ Concrete.readMemoryWord i x++index :: Int -> Buffer -> SWord8+index x (ConcreteBuffer b) = literal $ BS.index b x+index x (SymbolicBuffer b) = fromSized $ b !! x -- * Uninterpreted functions
src/EVM/TTY.hs view
@@ -40,7 +40,6 @@ import Data.ByteString (ByteString) import Data.Maybe (isJust, fromJust, fromMaybe) import Data.Map (Map, insert, lookupLT, singleton, filter)-import Data.Monoid ((<>)) import Data.Text (Text, pack) import Data.Text.Encoding (decodeUtf8) import Data.List (sort, find)@@ -850,19 +849,17 @@ isExecutionHalted _ vm = isJust (view result vm) currentSrcMap :: DappInfo -> VM -> Maybe SrcMap-currentSrcMap dapp vm =+currentSrcMap dapp vm = do+ this <- currentContract vm let- Just this = currentContract vm i = (view opIxMap this) SVec.! (view (state . pc) vm) h = view codehash this- in- case preview (dappSolcByHash . ix h) dapp of- Nothing ->- Nothing- Just (Creation, sol) ->- preview (creationSrcmap . ix i) sol- Just (Runtime, sol) ->- preview (runtimeSrcmap . ix i) sol+ srcmap <- preview (dappSolcByHash . ix h) dapp+ case srcmap of+ (Creation, sol) ->+ preview (creationSrcmap . ix i) sol+ (Runtime, sol) ->+ preview (runtimeSrcmap . ix i) sol drawStackPane :: UiVmState -> UiWidget drawStackPane ui =@@ -911,7 +908,7 @@ else withDefAttr boldAttr (opWidget x)) False (move $ list BytecodePane- (view codeOps (fromJust (currentContract vm)))+ (maybe mempty (view codeOps) (currentContract vm)) 1)
src/EVM/Transaction.hs view
@@ -15,7 +15,8 @@ import Data.Aeson (FromJSON (..)) import Data.ByteString (ByteString)-import Data.Map (Map)+import Data.Map (Map, keys)+import Data.Set (fromList) import Data.Maybe (fromMaybe, isNothing, isJust) import qualified Data.Aeson as JSON@@ -23,8 +24,17 @@ import qualified Data.ByteString as BS import qualified Data.Map as Map -data Transaction = Transaction- { txData :: ByteString,+data AccessListEntry = AccessListEntry {+ accessAddress :: Addr,+ accessStorageKeys :: [W256]+} deriving Show++data TxType = LegacyTransaction+ | AccessListTransaction+ deriving (Show, Eq)++data Transaction = Transaction {+ txData :: ByteString, txGasLimit :: W256, txGasPrice :: W256, txNonce :: W256,@@ -32,29 +42,45 @@ txS :: W256, txToAddr :: Maybe Addr, txV :: W256,- txValue :: W256- } deriving Show+ txValue :: W256,+ txType :: TxType,+ txAccessList :: [AccessListEntry]+} deriving Show +-- utility function for getting a more useful representation of accesslistentries+-- duplicates only matter for gas computation+-- ugly! could use a review....+txAccessMap :: Transaction -> Map Addr [W256]+txAccessMap tx = ((Map.fromListWith (++)) . makeTups) $ txAccessList tx+ where makeTups = map (\ale -> (accessAddress ale, accessStorageKeys ale))+ ecrec :: W256 -> W256 -> W256 -> W256 -> Maybe Addr-ecrec e v r s = (num . word) <$> EVM.Precompiled.execute 1 input 32+ecrec v r s e = 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+sender chainId tx = ecrec v' (txR tx) (txS tx) hash+ where hash = keccak (signingData chainId tx) v = txV tx v' = if v == 27 || v == 28 then v- else 28 - mod v 2+ else 27 + v signingData :: Int -> Transaction -> ByteString signingData chainId tx =- if v == (chainId * 2 + 35) || v == (chainId * 2 + 36)- then eip155Data- else normalData+ case txType tx of+ LegacyTransaction -> if v == (chainId * 2 + 35) || v == (chainId * 2 + 36)+ then eip155Data+ else normalData+ AccessListTransaction -> eip2930Data where v = fromIntegral (txV tx) to' = case txToAddr tx of Just a -> BS $ word160Bytes a Nothing -> BS mempty+ accessList = txAccessList tx+ rlpAccessList = EVM.RLP.List $ map (\accessEntry ->+ EVM.RLP.List [BS $ word160Bytes (accessAddress accessEntry), + EVM.RLP.List $ map rlpWordFull $ accessStorageKeys accessEntry]+ ) accessList normalData = rlpList [rlpWord256 (txNonce tx), rlpWord256 (txGasPrice tx), rlpWord256 (txGasLimit tx),@@ -70,7 +96,24 @@ rlpWord256 (fromIntegral chainId), rlpWord256 0x0, rlpWord256 0x0]+ eip2930Data = cons 0x01 $ rlpList [+ rlpWord256 (fromIntegral chainId),+ rlpWord256 (txNonce tx),+ rlpWord256 (txGasPrice tx),+ rlpWord256 (txGasLimit tx),+ to', + rlpWord256 (txValue tx),+ BS (txData tx),+ rlpAccessList] +accessListPrice :: FeeSchedule Integer -> [AccessListEntry] -> Integer+accessListPrice fs al =+ sum (map + (\ale -> + g_access_list_address fs + + (g_access_list_storage_key fs * (toInteger . length) (accessStorageKeys ale))) + al)+ txGasCost :: FeeSchedule Integer -> Transaction -> Integer txGasCost fs tx = let calldata = txData tx@@ -78,10 +121,21 @@ nonZeroBytes = BS.length calldata - zeroBytes baseCost = g_transaction fs + if isNothing (txToAddr tx) then g_txcreate fs else 0+ + (accessListPrice fs $ txAccessList tx) zeroCost = g_txdatazero fs nonZeroCost = g_txdatanonzero fs in baseCost + zeroCost * (fromIntegral zeroBytes) + nonZeroCost * (fromIntegral nonZeroBytes) +instance FromJSON AccessListEntry where+ parseJSON (JSON.Object val) = do+ accessAddress_ <- addrField val "address"+ --storageKeys <- (val JSON..: "storageKeys")+ --accessStorageKeys_ <- JSON.listParser (JSON.withText "W256" (return . readNull 0 . Text.unpack)) storageKeys+ accessStorageKeys_ <- (val JSON..: "storageKeys") >>= parseJSONList+ return $ AccessListEntry accessAddress_ accessStorageKeys_+ parseJSON invalid =+ JSON.typeMismatch "AccessListEntry" invalid+ instance FromJSON Transaction where parseJSON (JSON.Object val) = do tdata <- dataField val "data"@@ -93,7 +147,15 @@ toAddr <- addrFieldMaybe val "to" v <- wordField val "v" value <- wordField val "value"- return $ Transaction tdata gasLimit gasPrice nonce r s toAddr v value+ txType <- fmap read <$> (val JSON..:? "type")+ --let legacyTxn = Transaction tdata gasLimit gasPrice nonce r s toAddr v value+ case txType of+ Just 0x00 -> return $ Transaction tdata gasLimit gasPrice nonce r s toAddr v value LegacyTransaction []+ Just 0x01 -> do+ accessListEntries <- (val JSON..: "accessList") >>= parseJSONList+ return $ Transaction tdata gasLimit gasPrice nonce r s toAddr v value AccessListTransaction accessListEntries+ Just _ -> fail "unrecognized custom transaction type"+ Nothing -> return $ Transaction tdata gasLimit gasPrice nonce r s toAddr v value LegacyTransaction [] parseJSON invalid = JSON.typeMismatch "Transaction" invalid @@ -140,11 +202,6 @@ else touchAccount toAddr) $ preState - touched = if creation- then [origin]- else [origin, toAddr]- in vm & EVM.env . EVM.contracts .~ initState & EVM.tx . EVM.txReversion .~ preState- & EVM.tx . EVM.substate . EVM.touchedAccounts .~ touched
src/EVM/Types.hs view
@@ -9,17 +9,11 @@ import Prelude hiding (Word, LT, GT) -import Data.Aeson (FromJSON (..), (.:))--#if MIN_VERSION_aeson(1, 0, 0) import Data.Aeson (FromJSONKey (..), FromJSONKeyFunction (..)) import Data.Aeson-#endif- import Crypto.Hash import Data.SBV hiding (Word) import Data.Kind-import Data.Monoid ((<>)) import Data.Bifunctor (first) import Data.Char import Data.List (intercalate)@@ -33,7 +27,6 @@ import Data.DoubleWord import Data.DoubleWord.TH import Data.Maybe (fromMaybe)-import Data.Word (Word8) import Numeric (readHex, showHex) import Options.Generic import Control.Arrow ((>>>))@@ -371,7 +364,7 @@ showsPrec _ addr next = let hex = showHex addr next str = replicate (40 - length hex) '0' ++ hex- in "0x" ++ toChecksumAddress str+ in "0x" ++ toChecksumAddress str ++ drop 40 str instance Show SAddr where show (SAddr a) = case unliteral a of
src/EVM/UnitTest.hs view
@@ -45,10 +45,8 @@ import Data.Foldable (toList) import Data.Map (Map) import Data.Maybe (fromMaybe, catMaybes, fromJust, isJust, fromMaybe, mapMaybe)-import Data.Monoid ((<>)) import Data.Text (isPrefixOf, stripSuffix, intercalate, Text, pack, unpack) import Data.Text.Encoding (encodeUtf8)-import Data.Word (Word32) import System.Environment (lookupEnv) import System.IO (hFlush, stdout) @@ -763,7 +761,7 @@ let TestVMParams {..} = testParams vm = makeVm $ VMOpts- { vmoptContract = initialContract (InitCode (view creationCode theContract))+ { vmoptContract = initialContract (InitCode (ConcreteBuffer (view creationCode theContract))) , vmoptCalldata = (mempty, 0) , vmoptValue = 0 , vmoptAddress = testAddress@@ -778,10 +776,11 @@ , vmoptGasprice = testGasprice , vmoptMaxCodeSize = testMaxCodeSize , vmoptDifficulty = testDifficulty- , vmoptSchedule = FeeSchedule.istanbul+ , vmoptSchedule = FeeSchedule.berlin , vmoptChainId = testChainId , vmoptCreate = True , vmoptStorageModel = ConcreteS -- TODO: support RPC+ , vmoptTxAccessList = mempty -- TODO: support unit test access lists??? } creator = initialContract (RuntimeCode mempty)
src/EVM/VMTest.hs view
@@ -3,6 +3,7 @@ module EVM.VMTest ( Case+ , BlockchainCase #if MIN_VERSION_aeson(1, 0, 0) , parseBCSuite #endif@@ -27,6 +28,8 @@ import Control.Lens import Control.Monad +import GHC.Stack+ import Data.Aeson ((.:), FromJSON (..)) import Data.Foldable (fold) import Data.Map (Map)@@ -103,10 +106,10 @@ printContracts actual return okState -checkExpectation :: Bool -> Case -> EVM.VM -> IO Bool+checkExpectation :: HasCallStack => Bool -> Case -> EVM.VM -> IO Bool checkExpectation diff x vm = do let expectation = testExpectation x- let (okState, b2, b3, b4, b5) = checkExpectedContracts vm $ expectation+ (okState, b2, b3, b4, b5) = checkExpectedContracts vm $ expectation unless okState $ void $ checkStateFail diff x vm (okState, b2, b3, b4, b5) return okState@@ -118,9 +121,17 @@ 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'+ in and $ zipWith (===) (Map.elems padded_cs) (Map.elems padded_cs') -checkExpectedContracts :: EVM.VM -> Map Addr EVM.Contract -> (Bool, Bool, Bool, Bool, Bool)+(===) :: EVM.Contract -> EVM.Contract -> Bool+a === b = codeEqual && storageEqual && (view balance a == view balance b) && (view nonce a == view nonce b)+ where+ storageEqual = view storage a == view storage b+ codeEqual = case (view contractcode a, view contractcode b) of+ (EVM.RuntimeCode (ConcreteBuffer a'), EVM.RuntimeCode (ConcreteBuffer b')) -> a' == b'+ _ -> error "unexpected code"++checkExpectedContracts :: HasCallStack => EVM.VM -> Map Addr EVM.Contract -> (Bool, Bool, Bool, Bool, Bool) checkExpectedContracts vm expected = let cs = vm ^. EVM.env . EVM.contracts . to (fmap (clearZeroStorage.clearOrigStorage)) expectedCs = clearOrigStorage <$> expected@@ -156,7 +167,7 @@ instance FromJSON EVM.Contract where parseJSON (JSON.Object v) = do- code <- (EVM.RuntimeCode <$> (hexText <$> v .: "code"))+ code <- (EVM.RuntimeCode . ConcreteBuffer <$> (hexText <$> v .: "code")) storage' <- Map.mapKeys w256 <$> v .: "storage" balance' <- v .: "balance" nonce' <- v .: "nonce"@@ -211,7 +222,7 @@ filteredCases = Map.filter keepError allCases (erroredCases, parsedCases) = splitEithers filteredCases in if Map.size erroredCases > 0- then Left ("errored case: " ++ (show $ (Map.elems erroredCases) !! 0))+ then Left ("errored case: " ++ (show erroredCases)) else if Map.size parsedCases == 0 then Left "No cases to check." else Right parsedCases@@ -237,7 +248,7 @@ fromBlockchainCase :: BlockchainCase -> Either BlockchainError Case fromBlockchainCase (BlockchainCase blocks preState postState network) = case (blocks, network) of- ([block], "Istanbul") -> case blockTxs block of+ ([block], "Berlin") -> case blockTxs block of [tx] -> fromBlockchainCase' block tx preState postState [] -> Left NoTxs _ -> Left TooManyTxs@@ -248,8 +259,8 @@ -> Map Addr EVM.Contract -> Map Addr EVM.Contract -> Either BlockchainError Case fromBlockchainCase' block tx preState postState =- let isCreate = isNothing (txToAddr tx)- in case (sender 1 tx, checkTx tx preState) of+ let isCreate = isNothing (txToAddr tx) in+ case (sender 1 tx, checkTx tx preState) of (Nothing, _) -> Left SignatureUnverified (_, Nothing) -> Left (if isCreate then FailedCreate else InvalidTx) (Just origin, Just checkState) -> Right $ Case@@ -273,16 +284,17 @@ , vmoptChainId = 1 , vmoptCreate = isCreate , vmoptStorageModel = EVM.ConcreteS+ , vmoptTxAccessList = txAccessMap tx }) checkState postState where toAddr = fromMaybe (EVM.createAddress origin senderNonce) (txToAddr tx) senderNonce = EVM.wordValue $ view (accountAt origin . nonce) preState- feeSchedule = EVM.FeeSchedule.istanbul+ feeSchedule = EVM.FeeSchedule.berlin toCode = Map.lookup toAddr preState theCode = if isCreate- then EVM.InitCode (txData tx)+ then EVM.InitCode (ConcreteBuffer (txData tx)) else maybe (EVM.RuntimeCode mempty) (view contractcode) toCode cd = if isCreate then (mempty, 0)@@ -310,7 +322,7 @@ toAddr = fromMaybe (EVM.createAddress origin senderNonce) (txToAddr tx) prevCode = view (accountAt toAddr . contractcode) prestate prevNonce = view (accountAt toAddr . nonce) prestate- if isCreate && ((prevCode /= EVM.RuntimeCode mempty) || (prevNonce /= 0))+ if isCreate && ((case prevCode of {EVM.RuntimeCode b -> len b /= 0; _ -> True}) || (prevNonce /= 0)) then mzero else return $ prestate
test/test.hs view
@@ -1,4 +1,5 @@ {-# Language OverloadedStrings #-}+{-# Language ViewPatterns #-} {-# Language ScopedTypeVariables #-} {-# Language LambdaCase #-} {-# Language QuasiQuotes #-}@@ -7,6 +8,9 @@ {-# Language GeneralizedNewtypeDeriving #-} {-# Language DataKinds #-} {-# Language StandaloneDeriving #-}++module Main where+ import Data.Text (Text) import Data.ByteString (ByteString) @@ -14,34 +18,28 @@ import qualified Data.Text as Text import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as BS (fromStrict)+import qualified Data.ByteString.Lazy as BS (fromStrict, toStrict) import qualified Data.ByteString.Base16 as Hex import Test.Tasty-import Test.Tasty.QuickCheck-- hiding (forAll)+import Test.Tasty.QuickCheck import Test.Tasty.HUnit -import Control.Monad.State.Strict (execState, runState, when)-import Control.Lens hiding (List, pre, (.<), (.>))+import Control.Monad.State.Strict (execState, runState)+import Control.Lens hiding (List, pre, (.>)) import qualified Data.Vector as Vector import Data.String.Here import Control.Monad.Fail-import Debug.Trace-import Data.SBV.Tools.Overflow import Data.Binary.Put (runPut) import Data.SBV hiding ((===), forAll, sList) import Data.SBV.Control-import Data.SBV.Trans (sList)-import Data.SBV.List (implode)-import qualified Data.SBV.List as SL import qualified Data.Map as Map import Data.Binary.Get (runGetOrFail) import EVM hiding (Query) import EVM.SymExec-import EVM.Symbolic import EVM.ABI import EVM.Exec import qualified EVM.Patricia as Patricia@@ -75,6 +73,28 @@ , testCase "keccak256()" $ SolidityCall "x = uint(keccak256(abi.encodePacked(a)));" [AbiString ""] ===> AbiUInt 256 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470++ , testProperty "abi encoding vs. solidity" $ withMaxSuccess 20 $ forAll (arbitrary >>= genAbiValue) $+ \y -> ioProperty $ do+ -- traceM ("encoding: " ++ (show y) ++ " : " ++ show (abiValueType y))+ Just encoded <- runStatements [i| x = abi.encode(a);|]+ [y] AbiBytesDynamicType+ let AbiTuple (Vector.toList -> [solidityEncoded]) = decodeAbiValue (AbiTupleType $ Vector.fromList [AbiBytesDynamicType]) (BS.fromStrict encoded)+ let hevmEncoded = encodeAbiValue (AbiTuple $ Vector.fromList [y])+ -- traceM ("encoded (solidity): " ++ show solidityEncoded)+ -- traceM ("encoded (hevm): " ++ show (AbiBytesDynamic hevmEncoded))+ assertEqual "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded)++ , testProperty "abi encoding vs. solidity (2 args)" $ withMaxSuccess 20 $ forAll (arbitrary >>= bothM genAbiValue) $+ \(x', y') -> ioProperty $ do+ -- traceM ("encoding: " ++ (show x') ++ ", " ++ (show y') ++ " : " ++ show (abiValueType x') ++ ", " ++ show (abiValueType y'))+ Just encoded <- runStatements [i| x = abi.encode(a, b);|]+ [x', y'] AbiBytesDynamicType+ let AbiTuple (Vector.toList -> [solidityEncoded]) = decodeAbiValue (AbiTupleType $ Vector.fromList [AbiBytesDynamicType]) (BS.fromStrict encoded)+ let hevmEncoded = encodeAbiValue (AbiTuple $ Vector.fromList [x',y'])+ -- traceM ("encoded (solidity): " ++ show solidityEncoded)+ -- traceM ("encoded (hevm): " ++ show (AbiBytesDynamic hevmEncoded))+ assertEqual "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded) ] , testGroup "Precompiled contracts"@@ -506,7 +526,7 @@ let vm = vm0 & set (state . callvalue) 0 & over (env . contracts)- (Map.insert aAddr (initialContract (RuntimeCode a) &+ (Map.insert aAddr (initialContract (RuntimeCode $ ConcreteBuffer a) & set EVM.storage (EVM.Symbolic [] store))) verify vm Nothing Nothing (Just checkAssertions) putStrLn $ "found counterexample:"@@ -622,7 +642,7 @@ case runState exec (vmForEthrunCreation x) of (VMSuccess (ConcreteBuffer targetCode), vm1) -> do let target = view (state . contract) vm1- vm2 = execState (replaceCodeOfSelf (RuntimeCode targetCode)) vm1+ vm2 = execState (replaceCodeOfSelf (RuntimeCode (ConcreteBuffer targetCode))) vm1 return $ snd $ flip runState vm2 (do resetState assign (state . gas) 0xffffffffffffffff -- kludge@@ -638,6 +658,7 @@ singleContract :: Text -> Text -> IO (Maybe ByteString) singleContract x s = solidity x [i|+ pragma experimental ABIEncoderV2; contract ${x} { ${s} } |] @@ -672,10 +693,10 @@ (map (abiTypeSolidity . abiValueType) args) <> ")" runFunction [i|- function foo(${params}) public pure returns (${abiTypeSolidity t} x) {+ function foo(${params}) public pure returns (${abiTypeSolidity t} ${defaultDataLocation t} x) { ${stmts} }- |] (abiCalldata s (Vector.fromList args))+ |] (abiMethod s (AbiTuple $ Vector.fromList args)) getStaticAbiArgs :: VM -> [SWord 256] getStaticAbiArgs vm =@@ -683,7 +704,8 @@ bs = case cd of ConcreteBuffer bs' -> ConcreteBuffer $ BS.drop 4 bs' SymbolicBuffer bs' -> SymbolicBuffer $ drop 4 bs'- in decodeStaticArgs bs+ args = decodeStaticArgs bs+ in fmap (\(S _ v) -> v) args -- includes shaving off 4 byte function sig decodeAbiValues :: [AbiType] -> ByteString -> [AbiValue]@@ -725,3 +747,10 @@ assertEqual (Text.unpack s) (fmap Bytes (Just (encodeAbiValue x))) (fmap Bytes y)++bothM :: (Monad m) => (a -> m b) -> (a, a) -> m (b, b)+bothM f (a, a') = do+ b <- f a+ b' <- f a'+ return (b, b')+