Skip to content

Commit 07bc1b2

Browse files
authored
Merge pull request #681 from ethereum/equiv-fix-mate
Equivalence fixes
2 parents 64aa30c + c4082b3 commit 07bc1b2

File tree

12 files changed

+427
-112
lines changed

12 files changed

+427
-112
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3939
so we don't get too large buffers as counterexamples
4040
- More symbolic overapproximation for Balance and ExtCodeHash opcodes, fixing
4141
CodeHash SMT representation
42+
- Add deployment code flag to the `equivalenceCheck` function
4243
- PNeg + PGT/PGEq/PLeq/PLT simplification rules
4344
- We no longer dispatch Props to SMT that can be solved by a simplification
4445

@@ -57,6 +58,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5758
- CopySlice rewrite rule is now less strict while still being sound
5859
- Assumptions about reading from buffer after its size are now the same in all cases.
5960
Previously, they were too weak in case of reading 32 bytes.
61+
- The equivalence checker now is able to prove that an empty store is
62+
equivalent to a store with all slots initialized to 0.
63+
- Equivalence checking was incorrectly assuming that overapproximated values
64+
were sequentially equivalent. We now distinguish these symbolic values with
65+
`A-` and `B-`
6066

6167
## Changed
6268
- Warnings now lead printing FAIL. This way, users don't accidentally think that

cli/cli.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ data Command w
134134
, maxBranch :: w ::: Int <!> "100" <?> "Max number of branches to explore when encountering a symbolic value (default: 100)"
135135
, maxBufSize :: w ::: Int <!> "64" <?> "Maximum size of buffers such as calldata and returndata in exponents of 2 (default: 64, i.e. 2^64 bytes)"
136136
, promiseNoReent:: w ::: Bool <!> "Promise no reentrancy is possible into the contract(s) being examined"
137+
, create :: w ::: Bool <?> "Tx: creation"
137138
}
138139
| Exec -- Execute a given program with specified env & calldata
139140
{ code :: w ::: Maybe ByteString <?> "Program bytecode"
@@ -312,7 +313,7 @@ equivalence cmd = do
312313
cores <- liftIO $ unsafeInto <$> getNumProcessors
313314
let solverCount = fromMaybe cores cmd.numSolvers
314315
withSolvers solver solverCount (fromMaybe 1 cmd.solverThreads) cmd.smttimeout $ \s -> do
315-
(res, e) <- equivalenceCheck s (fromJust bytecodeA) (fromJust bytecodeB) veriOpts calldata
316+
(res, e) <- equivalenceCheck s (fromJust bytecodeA) (fromJust bytecodeB) veriOpts calldata cmd.create
316317
liftIO $ case (any isCex res, any Expr.isPartial e || any isUnknown res) of
317318
(False, False) -> putStrLn " \x1b[32m[PASS]\x1b[0m Contracts behave equivalently"
318319
(True, _) -> putStrLn " \x1b[31m[FAIL]\x1b[0m Contracts do not behave equivalently"

src/EVM.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,9 +1601,9 @@ freshBufFallback xs = do
16011601
freshVar <- use #freshVar
16021602
assign #freshVar (freshVar + 1)
16031603
let opName = pack $ show $ getOp ?op
1604-
let freshVarExpr = Var (opName <> "-result-stack-" <> (pack . show) freshVar)
1604+
let freshVarExpr = Var (opName <> "-result-stack-fresh-" <> (pack . show) freshVar)
16051605
modifying #constraints ((:) (PLEq freshVarExpr (Lit 1) ))
1606-
let freshReturndataExpr = AbstractBuf (opName <> "-result-data-" <> (pack . show) freshVar)
1606+
let freshReturndataExpr = AbstractBuf (opName <> "-result-data-fresh-" <> (pack . show) freshVar)
16071607
modifying #constraints ((:) (PLEq (bufLength freshReturndataExpr) (Lit (2 ^ ?conf.maxBufSize))))
16081608
assign (#state % #returndata) freshReturndataExpr
16091609
next >> assign (#state % #stack) (freshVarExpr:xs)
@@ -1617,7 +1617,7 @@ freshVarFallback xs _ = do
16171617
freshVar <- use #freshVar
16181618
assign #freshVar (freshVar + 1)
16191619
let opName = pack $ show $ getOp ?op
1620-
let freshVarExpr = Var (opName <> "-result-stack-" <> (pack . show) freshVar)
1620+
let freshVarExpr = Var (opName <> "-result-stack-fresh-" <> (pack . show) freshVar)
16211621
next >> assign (#state % #stack) (freshVarExpr:xs)
16221622

16231623
forceConcrete :: VMOps t => Expr EWord -> String -> (W256 -> EVM t s ()) -> EVM t s ()
@@ -2962,7 +2962,7 @@ instance VMOps Symbolic where
29622962
pushGas = do
29632963
modifying (#env % #freshGasVals) (+ 1)
29642964
n <- use (#env % #freshGasVals)
2965-
pushSym $ Expr.Gas n
2965+
pushSym $ Expr.Gas "" n
29662966
enoughGas _ _ = True
29672967
subGas _ _ = ()
29682968
toGas _ = ()

src/EVM/Format.hs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ module EVM.Format
2929
, strip0x'
3030
, hexByteString
3131
, hexText
32-
, bsToHex
3332
, showVal
3433
) where
3534

@@ -859,9 +858,6 @@ hexText t =
859858
Right x -> x
860859
_ -> internalError $ "invalid hex bytestring " ++ show t
861860

862-
bsToHex :: ByteString -> String
863-
bsToHex bs = concatMap (paddedShowHex 2) (BS.unpack bs)
864-
865861
showVal :: AbiValue -> Text
866862
showVal (AbiBytes _ bs) = formatBytes bs
867863
showVal (AbiAddress addr) = T.pack . show $ addr

src/EVM/SMT.hs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ instance Monoid CexVars where
8282
data BufModel
8383
= Comp CompressedBuf
8484
| Flat ByteString
85-
deriving (Eq, Show)
85+
deriving (Eq)
86+
instance Show BufModel where
87+
show (Comp c) = "Comp " <> show c
88+
show (Flat b) = "Flat 0x" <> bsToHex b
8689

8790
-- | This representation lets us store buffers of arbitrary length without
8891
-- exhausting the available memory, it closely matches the format used by
@@ -322,10 +325,10 @@ referencedFrameContext expr = nubOrd $ foldTerm go [] expr
322325
where
323326
go :: Expr a -> [(Builder, [Prop])]
324327
go = \case
325-
TxValue -> [(fromString "txvalue", [])]
326-
v@(Balance a) -> [(fromString "balance_" <> formatEAddr a, [PLT v (Lit $ 2 ^ (96 :: Int))])]
327-
Gas freshVar -> [(fromString ("gas_" <> show freshVar), [])]
328-
CodeHash a@(LitAddr _) -> [(fromString "codehash_" <> formatEAddr a, [])]
328+
o@TxValue -> [(fromRight' $ exprToSMT o, [])]
329+
o@(Balance _) -> [(fromRight' $ exprToSMT o, [PLT o (Lit $ 2 ^ (96 :: Int))])]
330+
o@(Gas _ _) -> [(fromRight' $ exprToSMT o, [])]
331+
o@(CodeHash (LitAddr _)) -> [(fromRight' $ exprToSMT o, [])]
329332
_ -> []
330333

331334
referencedBlockContext :: TraversableTerm a => a -> [(Builder, [Prop])]
@@ -443,20 +446,26 @@ declareConstrainAddrs names = SMT2 (["; concrete and symbolic addresses"] <> fma
443446
assume n = "(assert (bvugt " <> n <> " (_ bv9 160)))"
444447
cexvars = (mempty :: CexVars){ addrs = fmap toLazyText names }
445448

449+
-- The gas is a tuple of (prefix, index). Within each prefix, the gas is strictly decreasing as the
450+
-- index increases. This function gets a map of Prefix -> [Int], and for each prefix,
451+
-- enforces the order
446452
enforceGasOrder :: [Prop] -> SMT2
447-
enforceGasOrder ps = SMT2 (["; gas ordering"] <> order indices) mempty mempty
453+
enforceGasOrder ps = SMT2 (["; gas ordering"] <> (concatMap (uncurry order) indices)) mempty mempty
448454
where
449-
order :: [Int] -> [Builder]
450-
order n = consecutivePairs n >>= \(x, y)->
455+
order :: TS.Text -> [Int] -> [Builder]
456+
order prefix n = consecutivePairs (nubInt n) >>= \(x, y)->
451457
-- The GAS instruction itself costs gas, so it's strictly decreasing
452-
["(assert (bvugt gas_" <> (fromString . show $ x) <> " gas_" <> (fromString . show $ y) <> "))"]
458+
["(assert (bvugt " <> fromRight' (exprToSMT (Gas prefix x)) <> " " <>
459+
fromRight' ((exprToSMT (Gas prefix y))) <> "))"]
453460
consecutivePairs :: [Int] -> [(Int, Int)]
454461
consecutivePairs [] = []
455462
consecutivePairs l = zip l (tail l)
456-
indices :: [Int] = nubInt $ concatMap (foldProp go mempty) ps
457-
go :: Expr a -> [Int]
463+
indices = Map.toList $ toMapOfLists $ concatMap (foldProp go mempty) ps
464+
toMapOfLists :: [(TS.Text, Int)] -> Map.Map TS.Text [Int]
465+
toMapOfLists = foldr (\(k, v) acc -> Map.insertWith (++) k [v] acc) Map.empty
466+
go :: Expr a -> [(TS.Text, Int)]
458467
go e = case e of
459-
Gas freshVar -> [freshVar]
468+
Gas prefix v -> [(prefix, v)]
460469
_ -> []
461470

462471
declareFrameContext :: [(Builder, [Prop])] -> Err SMT2
@@ -872,8 +881,8 @@ exprToSMT = \case
872881
pure $ "(store" `sp` encPrev `sp` encIdx `sp` encVal <> ")"
873882
SLoad idx store -> op2 "select" store idx
874883
LitAddr n -> pure $ fromLazyText $ "(_ bv" <> T.pack (show (into n :: Integer)) <> " 160)"
875-
Gas freshVar -> pure $ fromLazyText $ "gas_" <> (T.pack $ show freshVar)
876884
CodeHash a@(LitAddr _) -> pure $ fromLazyText "codehash_" <> formatEAddr a
885+
Gas prefix var -> pure $ fromLazyText $ "gas_" <> T.pack (TS.unpack prefix) <> T.pack (show var)
877886

878887
a -> internalError $ "TODO: implement: " <> show a
879888
where
@@ -1060,14 +1069,12 @@ parseBlockCtx "prevrandao" = PrevRandao
10601069
parseBlockCtx "gaslimit" = GasLimit
10611070
parseBlockCtx "chainid" = ChainId
10621071
parseBlockCtx "basefee" = BaseFee
1063-
parseBlockCtx gas | TS.isPrefixOf (TS.pack "gas_") gas = Gas (textToInt $ TS.drop 4 gas)
10641072
parseBlockCtx val = internalError $ "cannot parse '" <> (TS.unpack val) <> "' into an Expr"
10651073

10661074
parseTxCtx :: TS.Text -> Expr EWord
10671075
parseTxCtx name
10681076
| name == "txvalue" = TxValue
10691077
| Just a <- TS.stripPrefix "balance_" name = Balance (parseEAddr a)
1070-
| Just a <- TS.stripPrefix "gas_" name = Gas (textToInt a)
10711078
| Just a <- TS.stripPrefix "codehash_" name = CodeHash (parseEAddr a)
10721079
| otherwise = internalError $ "cannot parse " <> (TS.unpack name) <> " into an Expr"
10731080

src/EVM/Solidity.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ solcRuntime contract src = do
394394
Just (Contracts sol, _, _) -> pure $ Map.lookup ("hevm.sol:" <> contract) sol <&> (.runtimeCode)
395395
Nothing -> internalError $ "unable to parse solidity output:\n" <> (T.unpack json)
396396

397+
397398
functionAbi :: Text -> IO Method
398399
functionAbi f = do
399400
json <- solc Solidity ("contract ABI { function " <> f <> " public {}}")

src/EVM/Solvers.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ checkMulti (SolverGroup taskQueue) smt2 multiSol = do
107107
-- collect result
108108
readChan resChan
109109

110-
checkSatWithProps :: App m => SolverGroup -> [Prop] ->m (CheckSatResult, Err SMT2)
110+
checkSatWithProps :: App m => SolverGroup -> [Prop] -> m (CheckSatResult, Err SMT2)
111111
checkSatWithProps (SolverGroup taskQueue) props = do
112112
conf <- readConfig
113113
let psSimp = simplifyProps props

0 commit comments

Comments
 (0)