Skip to content

Commit 4a3900e

Browse files
authored
Merge pull request #783 from ethereum/concretization-addmod-mulmod
Adding a number of serious improvements to dispatched SMT2 query
2 parents 7fa6a95 + c4a0afa commit 4a3900e

File tree

8 files changed

+108
-45
lines changed

8 files changed

+108
-45
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6161
via masking
6262
- More rewrite rules for (PLT (Lit 0) _) and (PEq (Lit 1) _)
6363
- Simplification can now be turned off from the cli via --no-simplify
64+
- When doing Keccak concretization, and simplification is enabled,
65+
we do both until fixedpoint
66+
- When gathering Keccak axioms, we simplify the bytestring that
67+
the keccak is applied to
68+
- More rewrite rules for MulMod, AddMod, SHL, SHR, SLT, and SignExtend
69+
- PLEq is now concretized in case it can be computed
70+
- More SignExtend test cases
6471

6572
## Fixed
6673
- We now try to simplify expressions fully before trying to cast them to a concrete value

src/EVM/Expr.hs

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ import EVM.Types
4040
maxLit :: W256
4141
maxLit = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
4242

43+
maxLitSigned :: W256
44+
maxLitSigned = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
45+
46+
minLitSigned :: W256
47+
minLitSigned = 0x1000000000000000000000000000000000000000000000000000000000000000
48+
4349
-- ** Stack Ops ** ---------------------------------------------------------------------------------
4450

4551

@@ -103,14 +109,12 @@ smod = op2 SMod (\x y ->
103109

104110
addmod :: Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord
105111
addmod = op3 AddMod (\x y z ->
106-
if z == 0
107-
then 0
112+
if z == 0 then 0
108113
else fromIntegral $ (into @Word512 x + into y) `Prelude.mod` into z)
109114

110115
mulmod :: Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord
111116
mulmod = op3 MulMod (\x y z ->
112-
if z == 0
113-
then 0
117+
if z == 0 then 0
114118
else fromIntegral $ (into @Word512 x * into y) `Prelude.mod` into z)
115119

116120
exp :: Expr EWord -> Expr EWord -> Expr EWord
@@ -203,13 +207,20 @@ sar = op2 SAR (\x y ->
203207

204208
peq :: (Typeable a) => Expr a -> Expr a -> Prop
205209
peq (Lit x) (Lit y) = PBool (x == y)
210+
peq (LitAddr x) (LitAddr y) = PBool (x == y)
206211
peq (LitByte x) (LitByte y) = PBool (x == y)
207212
peq (ConcreteBuf x) (ConcreteBuf y) = PBool (x == y)
208213
peq a b
209214
| a == b = PBool True
210215
| otherwise = let args = sort [a, b]
211216
in PEq (args !! 0) (args !! 1)
212217

218+
pleq :: Expr EWord -> Expr EWord -> Prop
219+
pleq (Lit a) (Lit b) = PBool (a <= b)
220+
pleq a b
221+
| a == b = PBool True
222+
| otherwise = PLEq a b
223+
213224
-- ** Bufs ** --------------------------------------------------------------------------------------
214225

215226

@@ -1057,21 +1068,9 @@ simplifyNoLitToKeccak e = untilFixpoint (mapExpr go) e
10571068

10581069
go (IndexWord a b) = indexWord a b
10591070

1060-
-- LT
1061-
go (EVM.Types.LT (Lit a) (Lit b))
1062-
| a < b = Lit 1
1063-
| otherwise = Lit 0
1064-
go (EVM.Types.LT _ (Lit 0)) = Lit 0
1065-
go (EVM.Types.LT a (Lit 1)) = iszero a
1066-
go (EVM.Types.LT (Lit 0) a) = iszero (Eq (Lit 0) a)
10671071

1068-
-- normalize all comparisons in terms of LT
1069-
go (EVM.Types.GT a b) = lt b a
1070-
go (EVM.Types.GEq a b) = leq b a
1071-
go (EVM.Types.LEq a b) = iszero (lt b a)
1072-
go (SLT a@(Lit _) b@(Lit _)) = slt a b
1073-
go (SGT a b) = SLT b a
10741072
go (SEx a (SEx a2 b)) | a == a2 = sex a b
1073+
go (SEx _ (Lit 0)) = Lit 0
10751074
go (SEx a b) = sex a b
10761075

10771076
-- IsZero
@@ -1106,14 +1105,6 @@ simplifyNoLitToKeccak e = untilFixpoint (mapExpr go) e
11061105
-- literal addresses
11071106
go (WAddr (LitAddr a)) = Lit $ into a
11081107

1109-
-- simple div/mod/add/sub
1110-
go (Div o1@(Lit _) o2@(Lit _)) = EVM.Expr.div o1 o2
1111-
go (SDiv o1@(Lit _) o2@(Lit _)) = EVM.Expr.sdiv o1 o2
1112-
go (Mod o1@(Lit _) o2@(Lit _)) = EVM.Expr.mod o1 o2
1113-
go (SMod o1@(Lit _) o2@(Lit _)) = EVM.Expr.smod o1 o2
1114-
go (Add o1@(Lit _) o2@(Lit _)) = EVM.Expr.add o1 o2
1115-
go (Sub o1@(Lit _) o2@(Lit _)) = EVM.Expr.sub o1 o2
1116-
11171108
-- Mod
11181109
go (Mod _ (Lit 0)) = Lit 0
11191110
go (SMod _ (Lit 0)) = Lit 0
@@ -1122,6 +1113,18 @@ simplifyNoLitToKeccak e = untilFixpoint (mapExpr go) e
11221113
go (Mod (Lit 0) _) = Lit 0
11231114
go (SMod (Lit 0) _) = Lit 0
11241115

1116+
-- MulMod
1117+
go (MulMod (Lit 0) _ _) = Lit 0
1118+
go (MulMod _ (Lit 0) _) = Lit 0
1119+
go (MulMod _ _ (Lit 0)) = Lit 0
1120+
go (MulMod a b c) = mulmod a b c
1121+
1122+
-- AddMod
1123+
go (AddMod (Lit 0) a b) = Mod a b
1124+
go (AddMod a (Lit 0) b) = Mod a b
1125+
go (AddMod _ _ (Lit 0)) = Lit 0
1126+
go (AddMod a b c) = addmod a b c
1127+
11251128
-- Triple And (must be before 3-way sort below)
11261129
go (And (Lit a) (And (Lit b) c)) = And (EVM.Expr.and (Lit a) (Lit b)) c
11271130

@@ -1207,9 +1210,11 @@ simplifyNoLitToKeccak e = untilFixpoint (mapExpr go) e
12071210
-- SHL / SHR by 0
12081211
go (SHL a v)
12091212
| a == (Lit 0) = v
1213+
| v == (Lit 0) = v
12101214
| otherwise = shl a v
12111215
go (SHR a v)
12121216
| a == (Lit 0) = v
1217+
| v == (Lit 0) = v
12131218
| otherwise = shr a v
12141219

12151220
-- Bitwise AND & OR. These MUST preserve bitwise equivalence
@@ -1286,6 +1291,29 @@ simplifyNoLitToKeccak e = untilFixpoint (mapExpr go) e
12861291
then Lit 0
12871292
else o
12881293

1294+
-- normalize all comparisons in terms of (S)LT
1295+
go (EVM.Types.GT a b) = lt b a
1296+
go (EVM.Types.GEq a b) = leq b a
1297+
go (EVM.Types.LEq a b) = iszero (lt b a)
1298+
go (SGT a b) = slt b a
1299+
1300+
-- LT
1301+
go (EVM.Types.LT _ (Lit 0)) = Lit 0
1302+
go (EVM.Types.LT a (Lit 1)) = iszero a
1303+
go (EVM.Types.LT (Lit 0) a) = iszero (Eq (Lit 0) a)
1304+
go (EVM.Types.LT a b) = lt a b
1305+
1306+
-- SLT
1307+
go (SLT _ (Lit a)) | a == minLitSigned = Lit 0
1308+
go (SLT (Lit a) _) | a == maxLitSigned = Lit 0
1309+
go (SLT a b) = slt a b
1310+
1311+
-- simple div/mod/add/sub
1312+
go (Div o1 o2) = EVM.Expr.div o1 o2
1313+
go (SDiv o1 o2) = EVM.Expr.sdiv o1 o2
1314+
go (Mod o1 o2) = EVM.Expr.mod o1 o2
1315+
go (SMod o1 o2) = EVM.Expr.smod o1 o2
1316+
12891317
go a = a
12901318

12911319

@@ -1330,6 +1358,7 @@ simplifyProp prop =
13301358
go (PLEq (Sub a b) c) | a == c = PLEq b a
13311359
go (PLT (Max (Lit a) b) (Lit c)) | a < c = PLT b (Lit c)
13321360
go (PLT (Lit 0) (Eq a b)) = peq a b
1361+
go (PLEq a b) = pleq a b
13331362

13341363
-- when it's PLT but comparison on the RHS then it's just (PEq 1 RHS)
13351364
go (PLT (Lit 0) (a@LT {})) = peq (Lit 1) a
@@ -1338,6 +1367,7 @@ simplifyProp prop =
13381367
go (PLT (Lit 0) (a@GT {})) = peq (Lit 1) a
13391368
go (PLT (Lit 0) (a@GEq {})) = peq (Lit 1) a
13401369
go (PLT (Lit 0) (a@SGT {})) = peq (Lit 1) a
1370+
go (POr (PLEq a1 (Lit b)) (PLEq (Lit c) a2)) | a1 == a2 && c == b+1 = PBool True
13411371

13421372
-- negations
13431373
go (PNeg (PBool b)) = PBool (Prelude.not b)
@@ -1348,6 +1378,7 @@ simplifyProp prop =
13481378
go (PNeg (PLEq a b)) = PGT a b
13491379
go (PNeg (PAnd a b)) = POr (PNeg a) (PNeg b)
13501380
go (PNeg (POr a b)) = PAnd (PNeg a) (PNeg b)
1381+
go (PNeg (PEq (Lit 1) (IsZero b))) = PEq (Lit 0) (IsZero b)
13511382

13521383
-- Empty buf
13531384
go (PEq (Lit 0) (BufLength k)) = peq k (ConcreteBuf "")
@@ -1739,6 +1770,10 @@ concKeccakSimpExpr orig = untilFixpoint (simplifyNoLitToKeccak . (mapExpr concKe
17391770
concKeccakProps :: [Prop] -> [Prop]
17401771
concKeccakProps orig = untilFixpoint (map (mapProp concKeccakOnePass)) orig
17411772

1773+
-- As above, but also simplify, to fixedpoint
1774+
concKeccakSimpProps :: [Prop] -> [Prop]
1775+
concKeccakSimpProps orig = untilFixpoint (simplifyProps . map (mapProp concKeccakOnePass)) orig
1776+
17421777
-- Simplifies in case the input to the Keccak is of specific array/map format and
17431778
-- can be simplified into a concrete value
17441779
-- Turns (Keccak ConcreteBuf) into a Lit

src/EVM/Fuzz.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import Test.QuickCheck.Random (mkQCGen)
2727
tryCexFuzz :: [Prop] -> Integer -> (Maybe (SMT.SMTCex))
2828
tryCexFuzz prePs tries = unGen (testVals tries) (mkQCGen 0) 1337
2929
where
30-
ps = Expr.simplifyProps $ Expr.concKeccakProps prePs
30+
ps = Expr.concKeccakSimpProps prePs
3131
vars = extractVars ps
3232
bufs = extractBufs ps
3333
stores = extractStorage ps
@@ -39,7 +39,7 @@ tryCexFuzz prePs tries = unGen (testVals tries) (mkQCGen 0) 1337
3939
storeVals <- getStores stores
4040
let
4141
ret = filterCorrectKeccak $ map (substituteEWord varvals . substituteBuf bufVals . substituteStores storeVals) ps
42-
retSimp = Expr.simplifyProps $ Expr.concKeccakProps ret
42+
retSimp = Expr.concKeccakSimpProps ret
4343
if null retSimp then pure $ Just (SMT.SMTCex {
4444
vars = varvals
4545
, addrs = mempty

src/EVM/Keccak.hs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ keccakAssumptions :: [Prop] -> [Expr Buf] -> [Expr Storage] -> [Prop]
6363
keccakAssumptions ps bufs stores = injectivity <> minValue <> minDiffOfPairs
6464
where
6565
(_, st) = runState (findKeccakPropsExprs ps bufs stores) initState
66-
67-
keccakPairs = uniquePairs (Set.toList st.keccakExprs)
66+
ks = Set.toList $ Set.map concretizeKeccakParam st.keccakExprs
67+
keccakPairs = uniquePairs ks
6868
injectivity = map injProp keccakPairs
69-
minValue = map minProp (Set.toList st.keccakExprs)
69+
minValue = map minProp ks
7070
minDiffOfPairs = map minDistance keccakPairs
7171
where
7272
minDistance :: (Expr EWord, Expr EWord) -> Prop
@@ -76,12 +76,16 @@ keccakAssumptions ps bufs stores = injectivity <> minValue <> minDiffOfPairs
7676
req2 = (PGEq (Sub kb ka) (Lit 256))
7777
minDistance _ = internalError "expected Keccak expression"
7878

79+
concretizeKeccakParam :: Expr EWord -> Expr EWord
80+
concretizeKeccakParam (Keccak buf) = Keccak (concKeccakSimpExpr buf)
81+
concretizeKeccakParam _ = internalError "Cannot happen"
82+
7983
compute :: forall a. Expr a -> Set Prop
8084
compute = \case
8185
e@(Keccak buf) -> do
8286
let b = simplify buf
8387
case keccak b of
84-
lit@(Lit _) -> Set.singleton (PEq lit e)
88+
lit@(Lit _) -> Set.singleton (PEq lit (concretizeKeccakParam e))
8589
_ -> Set.empty
8690
_ -> Set.empty
8791

src/EVM/SMT.hs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ collapse model = case toBuf model of
9292
getVar :: SMTCex -> TS.Text -> W256
9393
getVar cex name = fromJust $ Map.lookup (Var name) cex.vars
9494

95+
-- Props are ONLY for pretty printing the query Props
9596
data SMT2 = SMT2 [Builder] CexVars [Prop]
9697
deriving (Eq, Show)
9798

@@ -132,11 +133,13 @@ declareIntermediates bufs stores = do
132133
smt2Line :: Builder -> SMT2
133134
smt2Line txt = SMT2 [txt] mempty mempty
134135

135-
assertProps :: Config -> [Prop] -> Err SMT2
136136
-- simplify to rewrite sload/sstore combos
137-
-- notice: it is VERY important not to concretize, because Keccak assumptions
138-
-- will be generated by assertPropsNoSimp, and that needs unconcretized Props
139-
assertProps conf ps = assertPropsNoSimp (decompose . Expr.simplifyProps $ ps)
137+
-- notice: it is VERY important not to concretize early, because Keccak assumptions
138+
-- need unconcretized Props
139+
assertProps :: Config -> [Prop] -> Err SMT2
140+
assertProps conf ps =
141+
if not conf.simp then assertPropsHelper False ps
142+
else assertPropsHelper True (decompose . Expr.simplifyProps $ ps)
140143
where
141144
decompose :: [Prop] -> [Prop]
142145
decompose props = if conf.decomposeStorage && safeExprs && safeProps
@@ -147,11 +150,12 @@ assertProps conf ps = assertPropsNoSimp (decompose . Expr.simplifyProps $ ps)
147150
safeExprs = all (isJust . mapPropM_ Expr.safeToDecompose) props
148151
safeProps = all Expr.safeToDecomposeProp props
149152

153+
150154
-- Note: we need a version that does NOT call simplify,
151155
-- because we make use of it to verify the correctness of our simplification
152156
-- passes through property-based testing.
153-
assertPropsNoSimp :: [Prop] -> Err SMT2
154-
assertPropsNoSimp psPreConc = do
157+
assertPropsHelper :: Bool -> [Prop] -> Err SMT2
158+
assertPropsHelper simp psPreConc = do
155159
encs <- mapM propToSMT psElim
156160
intermediates <- declareIntermediates bufs stores
157161
readAssumes' <- readAssumes
@@ -177,12 +181,10 @@ assertPropsNoSimp psPreConc = do
177181
<> readAssumes'
178182
<> gasOrder
179183
<> smt2Line ""
180-
<> SMT2 (fmap (\p -> "(assert " <> p <> ")") encs) mempty mempty
181-
<> SMT2 mempty mempty { storeReads = storageReads } mempty
182-
<> SMT2 mempty mempty psPreConc
184+
<> SMT2 (fmap (\p -> "(assert " <> p <> ")") encs) (cexInfo storageReads) ps
183185

184186
where
185-
ps = Expr.concKeccakProps psPreConc
187+
ps = if simp then Expr.concKeccakSimpProps psPreConc else Expr.concKeccakProps psPreConc
186188
(psElim, bufs, stores) = eliminateProps ps
187189

188190
-- Props storing info that need declaration(s)
@@ -219,6 +221,10 @@ assertPropsNoSimp psPreConc = do
219221
assumps <- mapM assertSMT $ assertReads psElim bufs stores
220222
pure $ smt2Line "; read assumptions" <> SMT2 assumps mempty mempty
221223

224+
cexInfo :: Map (Expr 'EAddr, Maybe W256) (Set (Expr 'EWord)) -> CexVars
225+
cexInfo a = mempty { storeReads = a }
226+
227+
222228
referencedAbstractStores :: TraversableTerm a => a -> Set Builder
223229
referencedAbstractStores term = foldTerm go mempty term
224230
where

src/EVM/Solvers.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ checkSatWithProps sg props = do
106106
let psSimp = if conf.simp then simplifyProps props else props
107107
if psSimp == [PBool False] then pure (Qed, Right mempty)
108108
else do
109-
let smt2 = if conf.simp then assertProps conf psSimp else assertPropsNoSimp psSimp
109+
let smt2 = assertProps conf psSimp
110110
if isLeft smt2 then
111111
let err = getError smt2 in pure (Error err, Left err)
112112
else do
@@ -126,9 +126,9 @@ checkSatWithProps sg props = do
126126
readChan resChan
127127

128128
writeSMT2File :: SMT2 -> String -> IO ()
129-
writeSMT2File smt2 postfix =
129+
writeSMT2File smt2 postfix = do
130130
let content = formatSMT2 smt2 <> "\n\n(check-sat)"
131-
in T.writeFile ("query-" <> postfix <> ".smt2") content
131+
T.writeFile ("query-" <> postfix <> ".smt2") content
132132

133133
withSolvers :: App m => Solver -> Natural -> Natural -> Maybe Natural -> (SolverGroup -> m a) -> m a
134134
withSolvers solver count threads timeout cont = do

src/EVM/SymExec.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ interpret fetcher iterConf vm =
400400
-- ask the smt solver about the loop condition
401401
performQuery
402402
_ -> do
403-
let simpProps = Expr.simplifyProps $ Expr.concKeccakProps ((cond ./= Lit 0):preconds)
403+
let simpProps = Expr.concKeccakSimpProps ((cond ./= Lit 0):preconds)
404404
(r, vm') <- case simpProps of
405405
[PBool False] -> liftIO $ stToIO $ runStateT (continue (Case False)) vm
406406
[] -> liftIO $ stToIO $ runStateT (continue (Case True)) vm

test/test.hs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4467,6 +4467,17 @@ tests = testGroup "hevm"
44674467
assertEqualM "Must be 3-length" 3 (length simp2)
44684468
-- we run these without the simplifier inside `checkSatWithProps`, so
44694469
-- we can test the SMT solver's ability to handle sign extension
4470+
, test "sign-extend-conc-1" $ do
4471+
let p = Expr.sex (Lit 0) (Lit 0xff)
4472+
assertEqualM "stuff" p (Expr.simplify (Sub (Lit 0) (Lit 1)))
4473+
let p2 = Expr.sex (Lit 30) (Lit 0xff)
4474+
assertEqualM "stuff" p2 (Lit 0xff)
4475+
let p3 = Expr.sex (Lit 1) (Lit 0xff)
4476+
assertEqualM "stuff" p3 (Lit 0xff)
4477+
let p4 = Expr.sex (Lit 0) (Lit 0x1)
4478+
assertEqualM "stuff" p4 (Lit 0x1)
4479+
let p5 = Expr.sex (Lit 0) (Lit 0x0)
4480+
assertEqualM "stuff" p5 (Lit 0x0)
44704481
, testNoSimplify "sign-extend-1" $ do
44714482
let p = (PEq (Lit 1) (SLT (Lit 1774544) (SEx (Lit 2) (Lit 1774567))))
44724483
let simp = Expr.simplifyProps [p]

0 commit comments

Comments
 (0)