Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow dealing with abi.encodeWithSelector #625

Merged
merged 3 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Added
- JoinBytes simplification rule
- New simplification rule to help deal with abi.encodeWithSelector

## Fixed
- We now try to simplify expressions fully before trying to cast them to a concrete value
This should improve issues when "Unexpected Symbolic Arguments to Opcode" was
Expand Down
19 changes: 19 additions & 0 deletions src/EVM/Expr.hs
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,20 @@ simplify e = if (mapExpr go e == e)
where simplifiedBuf = BS.take (unsafeInto (n+sz)) buf
go (CopySlice a b c d f) = copySlice a b c d f

go (JoinBytes (LitByte a0) (LitByte a1) (LitByte a2) (LitByte a3)
(LitByte a4) (LitByte a5) (LitByte a6) (LitByte a7)
(LitByte a8) (LitByte a9) (LitByte a10) (LitByte a11)
(LitByte a12) (LitByte a13) (LitByte a14) (LitByte a15)
(LitByte a16) (LitByte a17) (LitByte a18) (LitByte a19)
(LitByte a20) (LitByte a21) (LitByte a22) (LitByte a23)
(LitByte a24) (LitByte a25) (LitByte a26) (LitByte a27)
(LitByte a28) (LitByte a29) (LitByte a30) (LitByte a31)) =
let b = map fromIntegral [a0, a1, a2, a3 ,a4, a5, a6, a7
,a8, a9, a10, a11 ,a12, a13, a14, a15
,a16, a17, a18, a19 ,a20, a21, a22, a23
,a24, a25, a26, a27 ,a28, a29, a30, a31]
in Lit (constructWord256 b)

go (IndexWord a b) = indexWord a b

-- LT
Expand Down Expand Up @@ -1418,6 +1432,11 @@ indexWord i@(Lit idx) e@(And (Lit mask) w)
fullWordMask = (2 ^ (256 :: W256)) - 1
unmaskedBytes = fromIntegral $ (countLeadingZeros mask) `Prelude.div` 8
isByteAligned m = (countLeadingZeros m) `Prelude.mod` 8 == 0
-- This pattern happens in Solidity for function selectors. Since Lit 0xfff... (28 bytes of 0xff)
-- is masking the function selector, it can be simplified to just the function selector bytes. Remember,
-- indexWord takes the MSB i-th byte when called with (indexWord i).
indexWord (Lit a) (Or funSel (And (Lit 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff) _)) | a < 4 =
indexWord (Lit a) funSel
Comment on lines +1438 to +1439
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I understand it correctly that this simplification is correct because the four most significant bytes of (And (Lit 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff) _) will always be zero?

indexWord (Lit idx) (Lit w)
| idx <= 31 = LitByte . fromIntegral $ shiftR w (248 - unsafeInto idx * 8)
| otherwise = LitByte 0
Expand Down
2 changes: 1 addition & 1 deletion src/EVM/Format.hs
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ formatPartial = \case
JumpIntoSymbolicCode pc idx -> "Encountered a jump into a potentially symbolic code region while executing initcode. pc: " <> pack (show pc) <> " jump dst: " <> pack (show idx)

formatSomeExpr :: SomeExpr -> Text
formatSomeExpr (SomeExpr e) = formatExpr e
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this change, because it's waaay nicer for the user, and we should be getting cleaner issues, too.

formatSomeExpr (SomeExpr e) = formatExpr $ Expr.simplify e

formatExpr :: Expr a -> Text
formatExpr = go
Expand Down
14 changes: 14 additions & 0 deletions src/EVM/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,20 @@ bssToBs :: ByteStringS -> ByteString
bssToBs (ByteStringS bs) = bs


-- Function to construct a W256 from a list of 32 Word8 values
constructWord256 :: [Word8] -> W256
constructWord256 bytes
| length bytes == 32 = W256 (Word256 (Word128 w256hi w256m1) (Word128 w256m0 w256lo))
| otherwise = internalError "List must contain exactly 32 Word8 values"
where
w256hi = word8sToWord64 (take 8 bytes)
w256m1 = word8sToWord64 (take 8 (drop 8 bytes))
w256m0 = word8sToWord64 (take 8 (drop 16 bytes))
w256lo = word8sToWord64 (take 8 (drop 24 bytes))
word8sToWord64 :: [Word8] -> Word64
word8sToWord64 = foldl (\acc byte -> (acc `shiftL` 8) .|. fromIntegral byte) 0


-- Keccak hashing ----------------------------------------------------------------------------------


Expand Down
39 changes: 39 additions & 0 deletions test/test.hs
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,11 @@ tests = testGroup "hevm"
, testProperty "simplifyProp-equivalence-sym" $ \(p) -> prop $ do
let simplified = Expr.simplifyProp p
checkEquivPropAndLHS p simplified
, testProperty "simplify-joinbytes" $ \(SymbolicJoinBytes exprList) -> prop $ do
let x = joinBytesFromList exprList
let simplified = Expr.simplify x
y <- checkEquiv x simplified
assertBoolM "Must be equal" y
, testProperty "simpProp-equivalence-sym-Prop" $ \(ps :: [Prop]) -> prop $ do
let simplified = pand (Expr.simplifyProps ps)
checkEquivPropAndLHS (pand ps) simplified
Expand Down Expand Up @@ -1306,6 +1311,22 @@ tests = testGroup "hevm"
r <- allBranchesFail c Nothing
assertBoolM "all branches must fail" (isRight r)
,
test "cheatcode-with-selector" $ do
Just c <- solcRuntime "C"
[i|
contract C {
function prove_warp_symbolic(uint128 jump) public {
uint pre = block.timestamp;
address hevm = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D;
(bool success, ) = hevm.call(abi.encodeWithSelector(bytes4(keccak256("warp(uint256)")), block.timestamp+jump));
require(success, "Call to hevm.warp failed");
assert(block.timestamp == pre + jump);
}
}
|]
Right e <- reachableUserAsserts c Nothing
assertBoolM "The expression should not contain Partial." $ Prelude.not $ Expr.containsNode isPartial e
,
test "call ffi when disabled" $ do
Just c <- solcRuntime "C"
[i|
Expand Down Expand Up @@ -4016,6 +4037,23 @@ instance Arbitrary (Expr EWord) where
instance Arbitrary (Expr Byte) where
arbitrary = sized genByte

newtype SymbolicJoinBytes = SymbolicJoinBytes [Expr Byte]
deriving (Eq, Show)

instance Arbitrary SymbolicJoinBytes where
arbitrary = liftM SymbolicJoinBytes $ replicateM 32 arbitrary

joinBytesFromList :: [Expr Byte] -> Expr EWord
joinBytesFromList [a0, a1, a2, a3, a4, a5, a6, a7,
a8, a9, a10, a11, a12, a13, a14, a15,
a16, a17, a18, a19, a20, a21, a22, a23,
a24, a25, a26, a27, a28, a29, a30, a31] =
JoinBytes a0 a1 a2 a3 a4 a5 a6 a7
a8 a9 a10 a11 a12 a13 a14 a15
a16 a17 a18 a19 a20 a21 a22 a23
a24 a25 a26 a27 a28 a29 a30 a31
joinBytesFromList _ = internalError "List must contain exactly 32 elements"

instance Arbitrary (Expr Buf) where
arbitrary = sized defaultBuf

Expand Down Expand Up @@ -4303,6 +4341,7 @@ genWord litFreq 0 = frequency
--, liftM2 SelfBalance arbitrary arbitrary
--, liftM2 Gas arbitrary arbitrary
, fmap Lit arbitrary
, fmap joinBytesFromList $ replicateM 32 arbitrary
, fmap Var (genName "word")
]
)
Expand Down
Loading