|
1 | 1 | module Echidna.SourceMapping where |
2 | 2 |
|
3 | 3 | import Control.Applicative ((<|>)) |
| 4 | +import Data.Bits (shiftL, (.|.)) |
4 | 5 | import Data.ByteString (ByteString) |
5 | 6 | import Data.ByteString qualified as BS |
6 | 7 | import Data.IORef (IORef, readIORef, atomicModifyIORef') |
7 | | -import Data.List (find) |
| 8 | +import Data.List (find, sortBy) |
| 9 | +import Data.Ord (Down(..)) |
8 | 10 | import Data.Map.Strict (Map) |
9 | 11 | import Data.Map.Strict qualified as Map |
10 | 12 | import Data.Maybe (mapMaybe) |
11 | 13 | import Data.Vector qualified as V |
| 14 | +import Data.Word (Word16) |
12 | 15 |
|
13 | 16 | import EVM.Dapp (DappInfo(..), findSrc) |
14 | 17 | import EVM.Expr (maybeLitByteSimp) |
@@ -88,12 +91,56 @@ findSrcByMetadata contr dapp = find compareMetadata (snd <$> Map.elems dapp.solc |
88 | 91 | findSrcForReal :: DappInfo -> Contract -> Maybe SolcContract |
89 | 92 | findSrcForReal dapp contr = findSrc contr dapp <|> findSrcByMetadata contr dapp |
90 | 93 |
|
| 94 | +-- | Find the position of CBOR length indicator after a metadata prefix. |
| 95 | +-- The length indicator is 2 bytes that encode the distance from prefix start to that position. |
| 96 | +-- Returns the position of the length indicator (not including the 2 bytes themselves). |
| 97 | +findCBORLength :: ByteString -> Int -> Maybe Int |
| 98 | +findCBORLength metadata prefixPos = go (prefixPos + 1) |
| 99 | + where |
| 100 | + go currentPos |
| 101 | + | currentPos + 2 > BS.length metadata = Nothing |
| 102 | + | otherwise = case readWord16BE metadata currentPos of |
| 103 | + Nothing -> Nothing |
| 104 | + Just lengthValue -> |
| 105 | + let distanceFromPrefix = currentPos - prefixPos |
| 106 | + in if fromIntegral lengthValue == distanceFromPrefix |
| 107 | + then Just currentPos |
| 108 | + else go (currentPos + 1) |
| 109 | + -- | Read 2 bytes at given position as big-endian Word16 |
| 110 | + readWord16BE :: ByteString -> Int -> Maybe Word16 |
| 111 | + readWord16BE bs pos |
| 112 | + | pos + 1 < BS.length bs = |
| 113 | + let b1 = fromIntegral (BS.index bs pos) :: Word16 |
| 114 | + b2 = fromIntegral (BS.index bs (pos + 1)) :: Word16 |
| 115 | + in Just $ (b1 `shiftL` 8) .|. b2 |
| 116 | + | otherwise = Nothing |
| 117 | + |
| 118 | +-- | Find all occurrences of any of the given prefixes in the bytecode, |
| 119 | +-- sorted by position descending (from end to start). |
| 120 | +findAllPrefixes :: ByteString -> [ByteString] -> [(Int, ByteString)] |
| 121 | +findAllPrefixes bs prefixes = |
| 122 | + sortBy (\a b -> compare (Down (fst a)) (Down (fst b))) $ concatMap findAll prefixes |
| 123 | + where |
| 124 | + findAll prefix = go 0 |
| 125 | + where |
| 126 | + go offset = case BS.breakSubstring prefix (BS.drop offset bs) of |
| 127 | + (_, rest) | BS.null rest -> [] |
| 128 | + (before, _) -> |
| 129 | + let pos = offset + BS.length before |
| 130 | + in (pos, prefix) : go (pos + 1) |
| 131 | + |
91 | 132 | getBytecodeMetadata :: ByteString -> ByteString |
92 | 133 | getBytecodeMetadata bs = |
93 | | - let stripCandidates = flip BS.breakSubstring bs <$> knownBzzrPrefixes in |
94 | | - case find ((/= mempty) . snd) stripCandidates of |
95 | | - Nothing -> bs -- if no metadata is found, return the complete bytecode |
96 | | - Just (_, m) -> m |
| 134 | + case firstValid (findAllPrefixes bs knownBzzrPrefixes) of |
| 135 | + Nothing -> bs -- if no valid metadata is found, return the complete bytecode |
| 136 | + Just metadata -> metadata |
| 137 | + where |
| 138 | + firstValid [] = Nothing |
| 139 | + firstValid ((pos, _prefix):rest) = |
| 140 | + case findCBORLength bs pos of |
| 141 | + Just lengthPos -> |
| 142 | + Just $ BS.drop pos (BS.take (lengthPos + 2) bs) |
| 143 | + Nothing -> firstValid rest |
97 | 144 |
|
98 | 145 | knownBzzrPrefixes :: [ByteString] |
99 | 146 | knownBzzrPrefixes = |
|
0 commit comments