@@ -61,37 +61,24 @@ op3 :: (Expr EWord -> Expr EWord -> Expr EWord -> Expr EWord)
6161op3 _ concrete (Lit x) (Lit y) (Lit z) = Lit (concrete x y z)
6262op3 symbolic _ x y z = symbolic x y z
6363
64- -- | If a given binary op is commutative, then we always force Lits to the lhs if
65- -- only one argument is a Lit. This makes writing pattern matches in the
66- -- simplifier easier .
64+ -- | If a given binary op is commutative, then we always order the arguments.
65+ -- This makes writing pattern matches in the simplifier easier.
66+ -- It will also force Lit to be the 1st argument, since Lit is the 1st element of Expr (see Types.hs) .
6767normArgs :: (Expr EWord -> Expr EWord -> Expr EWord ) -> (W256 -> W256 -> W256 ) -> Expr EWord -> Expr EWord -> Expr EWord
68- normArgs sym conc l r = case (l, r) of
69- (Lit _, _) -> doOp l r
70- (_, Lit _) -> doOp r l
71- _ -> doOp l r
72- where
73- doOp = op2 sym conc
74-
75- -- Same as `normArgs`, but for commutative operators it orders the arguments canonically
76- -- This helps in cases when (PEq x1 x2) is comparing x1 and x2 that are equivalent modulo commutativity.
77- -- In such cases, normArgsOrd will make sure x1 and x2 look the same.
78- -- NOTE: This is compatible with `Lit` being always on the left, as `Lit` is always
79- -- less than any other expression
80- normArgsOrd :: (Expr EWord -> Expr EWord -> Expr EWord ) -> (W256 -> W256 -> W256 ) -> Expr EWord -> Expr EWord -> Expr EWord
81- normArgsOrd sym conc l r = if l <= r then doOp l r else doOp r l
68+ normArgs sym conc l r = if l <= r then doOp l r else doOp r l
8269 where
8370 doOp = op2 sym conc
8471
8572-- Integers
8673
8774add :: Expr EWord -> Expr EWord -> Expr EWord
88- add = normArgsOrd Add (+)
75+ add = normArgs Add (+)
8976
9077sub :: Expr EWord -> Expr EWord -> Expr EWord
9178sub = op2 Sub (-)
9279
9380mul :: Expr EWord -> Expr EWord -> Expr EWord
94- mul = normArgsOrd Mul (*)
81+ mul = normArgs Mul (*)
9582
9683div :: Expr EWord -> Expr EWord -> Expr EWord
9784div = op2 Div (\ x y -> if y == 0 then 0 else Prelude. div x y)
@@ -166,21 +153,21 @@ sgt = op2 SGT (\x y ->
166153 in if sx > sy then 1 else 0 )
167154
168155eq :: Expr EWord -> Expr EWord -> Expr EWord
169- eq = normArgsOrd Eq (\ x y -> if x == y then 1 else 0 )
156+ eq = normArgs Eq (\ x y -> if x == y then 1 else 0 )
170157
171158iszero :: Expr EWord -> Expr EWord
172159iszero = op1 IsZero (\ x -> if x == 0 then 1 else 0 )
173160
174161-- Bits
175162
176163and :: Expr EWord -> Expr EWord -> Expr EWord
177- and = normArgsOrd And (.&.)
164+ and = normArgs And (.&.)
178165
179166or :: Expr EWord -> Expr EWord -> Expr EWord
180- or = normArgsOrd Or (.|.)
167+ or = normArgs Or (.|.)
181168
182169xor :: Expr EWord -> Expr EWord -> Expr EWord
183- xor = normArgsOrd Xor Data.Bits. xor
170+ xor = normArgs Xor Data.Bits. xor
184171
185172not :: Expr EWord -> Expr EWord
186173not = op1 Not complement
@@ -634,7 +621,7 @@ readStorage' loc store = case readStorage loc store of
634621-- test for an example where this happens. Note that decomposition solves this, though late in
635622-- the simplification lifecycle (just before SMT generation, which can be too late)
636623readStorage :: Expr EWord -> Expr Storage -> Maybe (Expr EWord )
637- readStorage w st = go (simplify w) st
624+ readStorage w st = go (simplifyNoLitToKeccak w) (simplifyNoLitToKeccak st)
638625 where
639626 go :: Expr EWord -> Expr Storage -> Maybe (Expr EWord )
640627 go _ (GVar _) = internalError " Can't read from a GVar"
@@ -644,21 +631,23 @@ readStorage w st = go (simplify w) st
644631 go slot s@ (SStore prevSlot val prev) = case (prevSlot, slot) of
645632 -- if address and slot match then we return the val in this write
646633 _ | prevSlot == slot -> Just val
647-
648- -- if the slots don't match (see previous guard) and are lits, we can skip this write
649- (Lit _, Lit _) -> go slot prev
634+ (a, b) | surelyEqual a b -> Just val
635+ (a, b) | surelyNotEqual a b -> go slot prev
650636
651637 -- slot is for a map + map -> skip write
652- (MappingSlot idA _, MappingSlot idB _) | BS. length idB == 64 && BS. length idA == 64 && idsDontMatch idA idB -> go slot prev
653- (MappingSlot idA keyA, MappingSlot idB keyB) | BS. length idB == 64 && BS. length idA == 64 && surelyNotEq keyA keyB -> go slot prev
638+ (MappingSlot idA _, MappingSlot idB _) | isMap' idB, isMap' idA, idsDontMatch idA idB -> go slot prev
639+ (MappingSlot idA keyA, MappingSlot idB keyB) | isMap' idB, isMap' idA, surelyNotEqual keyA keyB -> go slot prev
654640
655641 -- special case of array + map -> skip write
656- (ArraySlotWithOffs idA _, Keccak k) | bufLength k == Lit 64 && BS. length idA == 32 -> go slot prev
657- (ArraySlotZero idA, Keccak k) | bufLength k == Lit 64 && BS. length idA == 32 -> go slot prev
642+ (ArraySlotWithOffs idA _, Keccak k) | isMap k, isArray idA -> go slot prev
643+ (ArraySlotWithOffs2 idA _ _, Keccak k) | isMap k, isArray idA -> go slot prev
644+ (ArraySlotZero idA, Keccak k) | isMap k, isArray idA -> go slot prev
658645
659646 -- special case of map + array -> skip write
660- (Keccak k, ArraySlotWithOffs idA _) | bufLength k == Lit 64 && BS. length idA == 32 -> go slot prev
661- (ArraySlotWithOffs idA _, Keccak k) | bufLength k == Lit 64 && BS. length idA == 32 -> go slot prev
647+ (Keccak k, ArraySlotWithOffs idA _) | isMap k, isArray idA -> go slot prev
648+ (Keccak k, ArraySlotWithOffs2 idA _ _) | isMap k, isArray idA -> go slot prev
649+ (ArraySlotWithOffs idA _, Keccak k) | isMap k, isArray idA -> go slot prev
650+ (ArraySlotWithOffs2 idA _ _, Keccak k) | isMap k, isArray idA -> go slot prev
662651
663652 -- Fixed SMALL value will never match Keccak (well, it might, but that's VERY low chance)
664653 (Lit a, Keccak _) | a < 256 -> go slot prev
@@ -669,47 +658,58 @@ readStorage w st = go (simplify w) st
669658 -- occurring here is 2^32/2^256 = 2^-224, which is close enough to zero
670659 -- for our purposes. This lets us completely simplify reads from write
671660 -- chains involving writes to arrays at literal offsets.
672- (Lit a, Add (Lit b) (Keccak _) ) | a < 256 , b < maxW32 -> go slot prev
673- (Add (Lit a) (Keccak _) , Lit b) | b < 256 , a < maxW32 -> go slot prev
674-
675- --- NOTE these are needed to succeed in rewriting arrays with a variable index
676- -- (Lit a, Add (Keccak _) (Var _) ) | a < 256 -> go slot prev
677- -- (Add (Keccak _) (Var _) , Lit b) | b < 256 -> go slot prev
661+ (Lit a, Add (Lit b) (Keccak _)) | a < 256 , b < maxW32 -> go slot prev
662+ (Add (Lit a) (Keccak _) ,Lit b) | b < 256 , a < maxW32 -> go slot prev
678663
679664 -- Finding two Keccaks that are < 256 away from each other should be VERY hard
680665 -- This simplification allows us to deal with maps of structs
681666 (Add (Lit a2) (Keccak _), Add (Lit b2) (Keccak _)) | a2 /= b2, abs (a2- b2) < 256 -> go slot prev
682667 (Add (Lit a2) (Keccak _), (Keccak _)) | a2 > 0 , a2 < 256 -> go slot prev
683668 ((Keccak _), Add (Lit b2) (Keccak _)) | b2 > 0 , b2 < 256 -> go slot prev
684669
685- -- case of array + array, but different id's or different concrete offsets
686670 -- zero offs vs zero offs
687- (ArraySlotZero idA, ArraySlotZero idB) | BS. length idA == 32 , BS. length idB == 32 , idA /= idB -> go slot prev
671+ (ArraySlotZero idA, ArraySlotZero idB) | isArray idA, isArray idB, idA /= idB -> go slot prev
672+
688673 -- zero offs vs non-zero offs
689- (ArraySlotZero idA, ArraySlotWithOffs idB _) | BS. length idA == 32 , BS. length idB == 32 , idA /= idB -> go slot prev
690- (ArraySlotZero idA, ArraySlotWithOffs idB (Lit offB)) | BS. length idA == 32 , BS. length idB == 32 , offB /= 0 -> go slot prev
691- -- non-zero offs vs zero offs
692- (ArraySlotWithOffs idA _, ArraySlotZero idB) | BS. length idA == 32 , BS. length idB == 32 , idA /= idB -> go slot prev
693- (ArraySlotWithOffs idA (Lit offA), ArraySlotZero idB) | BS. length idA == 32 , BS. length idB == 32 , offA /= 0 -> go slot prev
694- -- non-zero offs vs non-zero offs
695- (ArraySlotWithOffs idA _, ArraySlotWithOffs idB _) | BS. length idA == 32 , BS. length idB == 32 , idA /= idB -> go slot prev
674+ (ArraySlotZero idA, ArraySlotWithOffs idB _) | isArray idA, isArray idB, idA /= idB -> go slot prev
675+ (ArraySlotZero idA, ArraySlotWithOffs idB (Lit offB)) | isArray idA, idA == idB, offB /= 0 -> go slot prev
676+ (ArraySlotZero idA, ArraySlotWithOffs2 idB _ _) | isArray idA, isArray idB, idA /= idB -> go slot prev
696677
697- (ArraySlotWithOffs idA offA, ArraySlotWithOffs idB offB) | BS. length idA == 32 , BS. length idB == 32 , surelyNotEq offA offB -> go slot prev
678+ -- non-zero offs vs zero offs
679+ (ArraySlotWithOffs idA _, ArraySlotZero idB) | isArray idA, isArray idB, idA /= idB -> go slot prev
680+ (ArraySlotWithOffs idA (Lit offA), ArraySlotZero idB) | isArray idA, idA == idB, offA /= 0 -> go slot prev
681+
682+ -- non-zero offs vs non-zero offs, different ids
683+ (ArraySlotWithOffs idA _, ArraySlotWithOffs idB _) | isArray idA, isArray idB, idA /= idB -> go slot prev
684+
685+ -- non-zero offs vs non-zero offs, same ids
686+ (ArraySlotWithOffs idA a, ArraySlotWithOffs idB b) | isArray idA, idA == idB,
687+ surelyNotEqual a b -> go slot prev
688+ (ArraySlotWithOffs idB offB2, ArraySlotWithOffs2 idA offA1 offA2) | isArray idA, idA == idB,
689+ surelyNotEqual (Add (Lit offA1) offA2) offB2 -> go slot prev
690+ (ArraySlotWithOffs2 idA offA1 offA2, ArraySlotWithOffs idB offB2) | isArray idA, idA == idB,
691+ surelyNotEqual (Add (Lit offA1) offA2) offB2 -> go slot prev
692+ (ArraySlotWithOffs2 idA offA1 offA2, ArraySlotWithOffs2 idB offB1 offB2) | isArray idA, idA == idB,
693+ surelyNotEqual (Add (Lit offA1) offA2) (Add (Lit offB1) offB2) -> go slot prev
698694
699695 -- we are unable to determine statically whether or not we can safely move deeper in the write chain, so return an abstract term
700696 _ -> Just $ SLoad slot s
701697
702- surelyNotEq :: Expr a -> Expr a -> Bool
703- surelyNotEq (Lit a) (Lit b) = a /= b
704- -- never equal: x+y (y is concrete) vs x+z (z is concrete), y!=z
705- surelyNotEq (Add (Lit l1) v1) (Add (Lit l2) v2) = l1 /= l2 && v1 == v2
706- -- never equal: x+y (y is concrete, non-zero) vs x
707- surelyNotEq v1 (Add (Lit l2) v2) = l2 /= 0 && v1 == v2
708- surelyNotEq (Add (Lit l1) v1) v2 = l1 /= 0 && v1 == v2
709- surelyNotEq _ _ = False
710-
711698 maxW32 :: W256
712699 maxW32 = into (maxBound :: Word32 )
700+ isArray :: ByteString -> Bool
701+ isArray b = BS. length b == 32
702+ isMap :: Expr Buf -> Bool
703+ isMap b = bufLength b == Lit 64
704+ isMap' :: ByteString -> Bool
705+ isMap' b = BS. length b == 64
706+ surelyNotEqual :: Expr EWord -> Expr EWord -> Bool
707+ surelyNotEqual a b = case (simplifyNoLitToKeccak (Sub a b)) of
708+ Lit k | k > 0 -> True
709+ _ -> False
710+ surelyEqual :: Expr EWord -> Expr EWord -> Bool
711+ surelyEqual a b = simplifyNoLitToKeccak (Sub a b) == Lit 0
712+
713713
714714-- storage slots for maps are determined by (keccak (bytes32(key) ++ bytes32(id)))
715715pattern MappingSlot :: ByteString -> Expr EWord -> Expr EWord
@@ -720,10 +720,12 @@ pattern MappingSlot idx key = Keccak (WriteWord (Lit 0) key (ConcreteBuf idx))
720720pattern ArraySlotWithOffs :: ByteString -> Expr EWord -> Expr EWord
721721pattern ArraySlotWithOffs id offset = Add offset (Keccak (ConcreteBuf id ))
722722
723+ pattern ArraySlotWithOffs2 :: ByteString -> W256 -> Expr EWord -> Expr EWord
724+ pattern ArraySlotWithOffs2 id offs1 offs2 = Add (Lit offs1) (Add offs2 (Keccak (ConcreteBuf id )))
725+
723726-- special pattern to match the 0th element because the `Add` term gets simplified out
724727pattern ArraySlotZero :: ByteString -> Expr EWord
725728pattern ArraySlotZero id = Keccak (ConcreteBuf id )
726-
727729-- checks if two mapping ids match or not
728730idsDontMatch :: ByteString -> ByteString -> Bool
729731idsDontMatch a b = BS. length a >= 64 && BS. length b >= 64 && diff32to64Byte a b
@@ -738,8 +740,8 @@ slotPos :: Word8 -> ByteString
738740slotPos pos = BS. pack ((replicate 31 (0 :: Word8 ))++ [pos])
739741
740742-- | Turns Literals into keccak(bytes32(id)) + offset (i.e. writes to arrays)
741- structureArraySlots :: Expr a -> Expr a
742- structureArraySlots e = mapExpr go e
743+ litToKeccak :: Expr a -> Expr a
744+ litToKeccak e = mapExpr go e
743745 where
744746 go :: Expr a -> Expr a
745747 go orig@ (Lit key) = case litToArrayPreimage key of
@@ -833,6 +835,7 @@ safeToDecompose inp = if result /= Mixed then Just () else Nothing
833835 Lit 64 -> setMap e
834836 _ -> setMixed e
835837 go e@ (SLoad (ArraySlotWithOffs x _) _) = if BS. length x == 32 then setArray e else setMixed e
838+ go e@ (SLoad (ArraySlotWithOffs2 x _ _) _) = if BS. length x == 32 then setArray e else setMixed e
836839 go e@ (SLoad (Lit x) _) | x < 256 = setSmall e
837840 go e@ (SLoad _ _) = setMixed e
838841 go e@ (SStore (MappingSlot x _) _ _) = if BS. length x == 64 then setMap e else setMixed e
@@ -841,6 +844,7 @@ safeToDecompose inp = if result /= Mixed then Just () else Nothing
841844 Lit 64 -> setMap e
842845 _ -> setMixed e
843846 go e@ (SStore (ArraySlotWithOffs x _) _ _) = if BS. length x == 32 then setArray e else setMixed e
847+ go e@ (SStore (ArraySlotWithOffs2 x _ _) _ _) = if BS. length x == 32 then setArray e else setMixed e
844848 go e@ (SStore (Lit x) _ _) | x < 256 = setSmall e
845849 go e@ (SStore _ _ _) = setMixed e
846850 go _ = pure ()
@@ -914,6 +918,7 @@ decomposeStorage = go
914918 (MappingSlot idx key) | BS. length idx == 64 -> Just (Just $ idxToWord idx, key)
915919 -- arrays
916920 (ArraySlotWithOffs idx offset) | BS. length idx == 32 -> Just (Just $ idxToWord64 idx, offset)
921+ (ArraySlotWithOffs2 idx offs1 offs2) | BS. length idx == 32 -> Just (Just $ idxToWord64 idx, Add (Lit offs1) offs2)
917922 (ArraySlotZero idx) | BS. length idx == 32 -> Just (Just $ idxToWord64 idx, Lit 0 )
918923 _ -> Nothing
919924
@@ -952,9 +957,10 @@ decomposeStorage = go
952957-- | Simple recursive match based AST simplification
953958-- Note: may not terminate!
954959simplify :: Expr a -> Expr a
955- simplify e = if (mapExpr go e == e)
956- then e
957- else simplify (mapExpr go (structureArraySlots e))
960+ simplify e = untilFixpoint (simplifyNoLitToKeccak . litToKeccak) e
961+
962+ simplifyNoLitToKeccak :: Expr a -> Expr a
963+ simplifyNoLitToKeccak e = untilFixpoint (mapExpr go) e
958964 where
959965 go :: Expr a -> Expr a
960966
@@ -1037,6 +1043,7 @@ simplify e = if (mapExpr go e == e)
10371043 | otherwise = Lit 0
10381044 go (EVM.Types. LT _ (Lit 0 )) = Lit 0
10391045 go (EVM.Types. LT a (Lit 1 )) = iszero a
1046+ go (EVM.Types. LT (Lit 0 ) a) = iszero (Eq (Lit 0 ) a)
10401047
10411048 -- normalize all comparisons in terms of LT
10421049 go (EVM.Types. GT a b) = lt b a
@@ -1120,16 +1127,29 @@ simplify e = if (mapExpr go e == e)
11201127 go (Add (Lit a) (Add (Lit b) x)) = add (Lit (a+ b)) x
11211128 go (Mul (Lit a) (Mul (Lit b) x)) = mul (Lit (a* b)) x
11221129 go (Xor (Lit a) (Xor (Lit b) x)) = EVM.Expr. xor (Lit (Data.Bits. xor a b)) x
1130+ go (Add (Add a b) c) = add (l !! 0 ) (add (l !! 1 ) (l !! 2 ))
1131+ where l = sort [a, b, c]
11231132 go (Add a (Add b c)) = add (l !! 0 ) (add (l !! 1 ) (l !! 2 ))
11241133 where l = sort [a, b, c]
1134+ go (Mul (Mul a b) c) = mul (l !! 0 ) (mul (l !! 1 ) (l !! 2 ))
1135+ where l = sort [a, b, c]
11251136 go (Mul a (Mul b c)) = mul (l !! 0 ) (mul (l !! 1 ) (l !! 2 ))
11261137 where l = sort [a, b, c]
1138+ go (Xor (Xor a b) c) = x (l !! 0 ) (x (l !! 1 ) (l !! 2 ))
1139+ where l = sort [a, b, c]
1140+ x = EVM.Expr. xor
11271141 go (Xor a (Xor b c)) = x (l !! 0 ) (x (l !! 1 ) (l !! 2 ))
11281142 where l = sort [a, b, c]
11291143 x = EVM.Expr. xor
1144+ go (Or (Or a b) c) = o (l !! 0 ) (o (l !! 1 ) (l !! 2 ))
1145+ where l = sort [a, b, c]
1146+ o = EVM.Expr. or
11301147 go (Or a (Or b c)) = o (l !! 0 ) (o (l !! 1 ) (l !! 2 ))
11311148 where l = sort [a, b, c]
11321149 o = EVM.Expr. or
1150+ go (And (And a b) c) = an (l !! 0 ) (an (l !! 1 ) (l !! 2 ))
1151+ where l = sort [a, b, c]
1152+ an = EVM.Expr. and
11331153 go (And a (And b c)) = an (l !! 0 ) (an (l !! 1 ) (l !! 2 ))
11341154 where l = sort [a, b, c]
11351155 an = EVM.Expr. and
@@ -1259,9 +1279,11 @@ simplifyProp prop =
12591279 where
12601280 go :: Prop -> Prop
12611281
1262- -- LT/LEq comparisons
1282+ -- Rewrite PGT/GEq to PLT/PLEq
12631283 go (PGT a b) = PLT b a
12641284 go (PGEq a b) = PLEq b a
1285+
1286+ -- PLT/PLEq comparisons
12651287 go (PLT (Var _) (Lit 0 )) = PBool False
12661288 go (PLEq (Lit 0 ) _) = PBool True
12671289 go (PLEq (WAddr _) (Lit 1461501637330902918203684832716283019655932542975 )) = PBool True
@@ -1289,8 +1311,14 @@ simplifyProp prop =
12891311 -- Empty buf
12901312 go (PEq (Lit 0 ) (BufLength k)) = peq k (ConcreteBuf " " )
12911313
1314+ -- PEq rewrites (notice -- GT/GEq is always rewritten to LT by simplify)
1315+ go (PEq (Lit 1 ) (IsZero (LT a b))) = PLT a b
1316+ go (PEq (Lit 1 ) (IsZero (LEq a b))) = PLEq a b
1317+ go (PEq (Lit 0 ) (IsZero a)) = PLT (Lit 0 ) a
1318+ go (PEq a1 (Add a2 y)) | a1 == a2 = peq y (Lit 0 )
1319+
12921320 -- solc specific stuff
1293- go (PEq (Lit 0 ) (IsZero (IsZero ( Eq a b) ))) = PNeg (peq a b)
1321+ go (PLT (Lit 0 ) (IsZero (Eq a b))) = PNeg (peq a b)
12941322
12951323 -- iszero(a) -> (a == 0)
12961324 -- iszero(iszero(a))) -> ~(a == 0) -> a > 0
@@ -1324,18 +1352,16 @@ simplifyProp prop =
13241352 go (PImpl (PBool True ) b) = b
13251353 go (PImpl (PBool False ) _) = PBool True
13261354
1327- -- Double negation
1328- go (PEq (Lit 0 ) (IsZero (Eq a b))) = peq a b
1329- go (PEq (Lit 0 ) (IsZero (LT a b))) = PLT a b
1330- go (PEq (Lit 0 ) (IsZero (GT a b))) = PGT a b
1331- go (PEq (Lit 0 ) (IsZero (LEq a b))) = PLEq a b
1332- go (PEq (Lit 0 ) (IsZero (GEq a b))) = PGEq a b
1355+ -- Double negation (no need for GT/GEq, as it's rewritten to LT/LEq)
1356+ go (PLT (Lit 0 ) (LT a b)) = PLT a b
1357+ go (PLT (Lit 0 ) (LEq a b)) = PLEq a b
13331358
13341359 -- Eq
13351360 go (PEq (Lit 0 ) (Eq a b)) = PNeg (peq a b)
13361361 go (PEq (Lit 1 ) (Eq a b)) = peq a b
13371362 go (PEq (Lit 0 ) (Sub a b)) = peq a b
13381363 go (PEq (Lit 0 ) (LT a b)) = PLEq b a
1364+ go (PEq (Lit 0 ) (LEq a b)) = PLT b a
13391365 go (PEq l r) = peq l r
13401366
13411367 go p = p
@@ -1656,7 +1682,7 @@ constPropagate ps =
16561682
16571683-- Concretize & simplify Keccak expressions until fixed-point.
16581684concKeccakSimpExpr :: Expr a -> Expr a
1659- concKeccakSimpExpr orig = untilFixpoint ((mapExpr concKeccakOnePass) . simplify) orig
1685+ concKeccakSimpExpr orig = untilFixpoint (simplifyNoLitToKeccak . (mapExpr concKeccakOnePass)) ( simplify orig)
16601686
16611687-- Only concretize Keccak in Props
16621688-- Needed because if it also simplified, we may not find some simplification errors, as
0 commit comments