@@ -14,7 +14,7 @@ import Control.Monad.Reader (MonadReader, ask, asks)
14
14
import Control.Monad.ST (ST , stToIO , RealWorld )
15
15
import Data.Bits
16
16
import Data.ByteString qualified as BS
17
- import Data.IORef (readIORef , atomicWriteIORef , newIORef , writeIORef , modifyIORef' )
17
+ import Data.IORef (IORef , readIORef , atomicWriteIORef , newIORef , writeIORef , modifyIORef' )
18
18
import Data.Map qualified as Map
19
19
import Data.Maybe (fromMaybe , fromJust )
20
20
import Data.Text qualified as T
@@ -102,91 +102,8 @@ execTxWith executeTx tx = do
102
102
pure (vmResult, gasLeftBeforeTx - gasLeftAfterTx)
103
103
where
104
104
runFully = do
105
- config <- asks (. cfg)
106
- -- TODO: Is the latest block a good default? It makes fuzzing hard to reproduce. Rethink this.
107
- let rpcBlock = maybe EVM.Fetch. Latest (EVM.Fetch. BlockNumber . fromIntegral ) config. rpcBlock
108
-
109
105
vmResult <- executeTx
110
- -- For queries, we halt execution because the VM needs some additional
111
- -- information from the outside. We provide this information and resume
112
- -- the execution by recursively calling `runFully`.
113
- case getQuery vmResult of
114
- -- A previously unknown contract is required
115
- Just q@ (PleaseFetchContract addr _ continuation) -> do
116
- cacheRef <- asks (. fetchContractCache)
117
- cache <- liftIO $ readIORef cacheRef
118
- case Map. lookup addr cache of
119
- Just (Just contract) -> fromEVM (continuation contract)
120
- Just Nothing -> do
121
- v <- get
122
- v' <- liftIO $ stToIO $ execStateT (continuation emptyAccount) v
123
- put v'
124
- Nothing -> do
125
- logMsg $ " INFO: Performing RPC: " <> show q
126
- case config. rpcUrl of
127
- Just rpcUrl -> do
128
- ret <- liftIO $ safeFetchContractFrom rpcBlock rpcUrl addr
129
- case ret of
130
- -- TODO: fix hevm to not return an empty contract in case of an error
131
- Just contract | contract. code /= RuntimeCode (ConcreteRuntimeCode " " ) -> do
132
- fromEVM (continuation contract)
133
- liftIO $ atomicWriteIORef cacheRef $ Map. insert addr (Just contract) cache
134
- _ -> do
135
- -- TODO: better error reporting in HEVM, when intermittent
136
- -- network error then retry
137
- liftIO $ atomicWriteIORef cacheRef $ Map. insert addr Nothing cache
138
- logMsg $ " ERROR: Failed to fetch contract: " <> show q
139
- -- TODO: How should we fail here? It could be a network error,
140
- -- RPC server returning junk etc.
141
- fromEVM (continuation emptyAccount)
142
- Nothing -> do
143
- liftIO $ atomicWriteIORef cacheRef $ Map. insert addr Nothing cache
144
- logMsg $ " ERROR: Requested RPC but it is not configured: " <> show q
145
- -- TODO: How should we fail here? RPC is not configured but VM
146
- -- wants to fetch
147
- fromEVM (continuation emptyAccount)
148
- runFully -- resume execution
149
-
150
- -- A previously unknown slot is required
151
- Just q@ (PleaseFetchSlot addr slot continuation) -> do
152
- cacheRef <- asks (. fetchSlotCache)
153
- cache <- liftIO $ readIORef cacheRef
154
- case Map. lookup addr cache >>= Map. lookup slot of
155
- Just (Just value) -> fromEVM (continuation value)
156
- Just Nothing -> fromEVM (continuation 0 )
157
- Nothing -> do
158
- logMsg $ " INFO: Performing RPC: " <> show q
159
- case config. rpcUrl of
160
- Just rpcUrl -> do
161
- ret <- liftIO $ safeFetchSlotFrom rpcBlock rpcUrl addr slot
162
- case ret of
163
- Just value -> do
164
- fromEVM (continuation value)
165
- liftIO $ atomicWriteIORef cacheRef $
166
- Map. insertWith Map. union addr (Map. singleton slot (Just value)) cache
167
- Nothing -> do
168
- -- TODO: How should we fail here? It could be a network error,
169
- -- RPC server returning junk etc.
170
- logMsg $ " ERROR: Failed to fetch slot: " <> show q
171
- liftIO $ atomicWriteIORef cacheRef $
172
- Map. insertWith Map. union addr (Map. singleton slot Nothing ) cache
173
- fromEVM (continuation 0 )
174
- Nothing -> do
175
- logMsg $ " ERROR: Requested RPC but it is not configured: " <> show q
176
- -- Use the zero slot
177
- fromEVM (continuation 0 )
178
- runFully -- resume execution
179
-
180
- -- Execute a FFI call
181
- Just (PleaseDoFFI (cmd : args) continuation) -> do
182
- (_, stdout, _) <- liftIO $ readProcessWithExitCode cmd args " "
183
- let encodedResponse = encodeAbiValue $
184
- AbiTuple (V. fromList [AbiBytesDynamic . hexText . T. pack $ stdout])
185
- fromEVM (continuation encodedResponse)
186
- runFully
187
-
188
- -- No queries to answer, the tx is fully executed and the result is final
189
- _ -> pure vmResult
106
+ maybe (pure vmResult) (\ q -> handleQuery q >> runFully) (getQuery vmResult)
190
107
191
108
-- | Handles reverts, failures and contract creations that might be the result
192
109
-- (`vmResult`) of executing transaction `tx`.
@@ -217,6 +134,92 @@ execTxWith executeTx tx = do
217
134
modify' $ execState $ loadContract (LitAddr tx. dst)
218
135
_ -> pure ()
219
136
137
+ getRpcInfo = do
138
+ config <- asks (. cfg)
139
+ -- TODO: Is the latest block a good default? It makes fuzzing hard to reproduce. Rethink this.
140
+ let rpcBlock = maybe EVM.Fetch. Latest (EVM.Fetch. BlockNumber . fromIntegral ) config. rpcBlock
141
+ return (config. rpcUrl, rpcBlock)
142
+
143
+
144
+ -- For queries, we halt execution because the VM needs some additional
145
+ -- information from the outside. We provide this information, and then
146
+ -- the execution is resumed.
147
+
148
+ -- A previously unknown contract is required
149
+ handleQuery q@ (PleaseFetchContract addr _ continuation) = do
150
+ cacheRef <- asks (. fetchContractCache)
151
+ cache <- liftIO $ readIORef cacheRef
152
+ case Map. lookup addr cache of
153
+ Just (Just contract) -> fromEVM (continuation contract)
154
+ Just Nothing -> do
155
+ v <- get
156
+ v' <- liftIO $ stToIO $ execStateT (continuation emptyAccount) v
157
+ put v'
158
+ Nothing -> do
159
+ logMsg $ " INFO: Performing RPC: " <> show q
160
+ (maybeRpcUrl, rpcBlock) <- getRpcInfo
161
+ case maybeRpcUrl of
162
+ Just rpcUrl -> do
163
+ ret <- liftIO $ safeFetchContractFrom rpcBlock rpcUrl addr
164
+ case ret of
165
+ -- TODO: fix hevm to not return an empty contract in case of an error
166
+ Just contract | contract. code /= RuntimeCode (ConcreteRuntimeCode " " ) -> do
167
+ fromEVM (continuation contract)
168
+ liftIO $ atomicWriteIORef cacheRef $ Map. insert addr (Just contract) cache
169
+ _ -> do
170
+ -- TODO: better error reporting in HEVM, when intermittent
171
+ -- network error then retry
172
+ liftIO $ atomicWriteIORef cacheRef $ Map. insert addr Nothing cache
173
+ logMsg $ " ERROR: Failed to fetch contract: " <> show q
174
+ -- TODO: How should we fail here? It could be a network error,
175
+ -- RPC server returning junk etc.
176
+ fromEVM (continuation emptyAccount)
177
+ Nothing -> do
178
+ liftIO $ atomicWriteIORef cacheRef $ Map. insert addr Nothing cache
179
+ logMsg $ " ERROR: Requested RPC but it is not configured: " <> show q
180
+ -- TODO: How should we fail here? RPC is not configured but VM
181
+ -- wants to fetch
182
+ fromEVM (continuation emptyAccount)
183
+
184
+ -- A previously unknown slot is required
185
+ handleQuery q@ (PleaseFetchSlot addr slot continuation) = do
186
+ cacheRef <- asks (. fetchSlotCache)
187
+ cache <- liftIO $ readIORef cacheRef
188
+ case Map. lookup addr cache >>= Map. lookup slot of
189
+ Just (Just value) -> fromEVM (continuation value)
190
+ Just Nothing -> fromEVM (continuation 0 )
191
+ Nothing -> do
192
+ logMsg $ " INFO: Performing RPC: " <> show q
193
+ (maybeRpcUrl, rpcBlock) <- getRpcInfo
194
+ case maybeRpcUrl of
195
+ Just rpcUrl -> do
196
+ ret <- liftIO $ safeFetchSlotFrom rpcBlock rpcUrl addr slot
197
+ case ret of
198
+ Just value -> do
199
+ fromEVM (continuation value)
200
+ liftIO $ atomicWriteIORef cacheRef $
201
+ Map. insertWith Map. union addr (Map. singleton slot (Just value)) cache
202
+ Nothing -> do
203
+ -- TODO: How should we fail here? It could be a network error,
204
+ -- RPC server returning junk etc.
205
+ logMsg $ " ERROR: Failed to fetch slot: " <> show q
206
+ liftIO $ atomicWriteIORef cacheRef $
207
+ Map. insertWith Map. union addr (Map. singleton slot Nothing ) cache
208
+ fromEVM (continuation 0 )
209
+ Nothing -> do
210
+ logMsg $ " ERROR: Requested RPC but it is not configured: " <> show q
211
+ -- Use the zero slot
212
+ fromEVM (continuation 0 )
213
+
214
+ -- Execute a FFI call
215
+ handleQuery (PleaseDoFFI (cmd : args) continuation) = do
216
+ (_, stdout, _) <- liftIO $ readProcessWithExitCode cmd args " "
217
+ let encodedResponse = encodeAbiValue $
218
+ AbiTuple (V. fromList [AbiBytesDynamic . hexText . T. pack $ stdout])
219
+ fromEVM (continuation encodedResponse)
220
+
221
+ handleQuery (PleaseDoFFI [] _) = error " Malformed FFI call"
222
+
220
223
logMsg :: (MonadIO m , MonadReader Env m ) => String -> m ()
221
224
logMsg msg = do
222
225
cfg <- asks (. cfg)
@@ -262,63 +265,70 @@ execTxWithCov tx = do
262
265
_ -> pure False
263
266
264
267
pure (r, grew || grew')
268
+
269
+ -- | The same as EVM.exec but collects coverage, will stop on a query
270
+ execCov
271
+ :: (MonadIO m , MonadState (VM Concrete RealWorld ) m , MonadThrow m )
272
+ => Env
273
+ -> IORef (Bool , Maybe (VMut. IOVector CoverageInfo , Int ))
274
+ -> m (VMResult Concrete RealWorld )
275
+ execCov env covContextRef = do
276
+ vm <- get
277
+ (r, vm') <- liftIO $ loop vm
278
+ put vm'
279
+ pure r
265
280
where
266
- -- the same as EVM.exec but collects coverage, will stop on a query
267
- execCov env covContextRef = do
268
- vm <- get
269
- (r, vm') <- liftIO $ loop vm
270
- put vm'
271
- pure r
272
- where
273
- -- | Repeatedly exec a step and add coverage until we have an end result
274
- loop :: VM Concrete RealWorld -> IO (VMResult Concrete RealWorld , VM Concrete RealWorld )
275
- loop ! vm = case vm. result of
276
- Nothing -> do
277
- addCoverage vm
278
- stepVM vm >>= loop
279
- Just r -> pure (r, vm)
280
-
281
- -- | Execute one instruction on the EVM
282
- stepVM :: VM Concrete RealWorld -> IO (VM Concrete RealWorld )
283
- stepVM = stToIO . execStateT exec1
284
-
285
- -- | Add current location to the CoverageMap
286
- addCoverage :: VM Concrete RealWorld -> IO ()
287
- addCoverage ! vm = do
288
- let (pc, opIx, depth) = currentCovLoc vm
289
- contract = currentContract vm
290
-
291
- maybeCovVec <- lookupUsingCodehashOrInsert env. codehashMap contract env. dapp env. coverageRef $ do
292
- let size = BS. length . forceBuf . fromJust . view bytecode $ contract
293
- if size == 0 then pure Nothing else do
294
- -- IO for making a new vec
295
- vec <- VMut. new size
296
- -- We use -1 for opIx to indicate that the location was not covered
297
- forM_ [0 .. size- 1 ] $ \ i -> VMut. write vec i (- 1 , 0 , 0 )
298
- pure $ Just vec
299
-
300
- case maybeCovVec of
301
- Nothing -> pure ()
302
- Just vec -> do
303
- -- TODO: no-op when pc is out-of-bounds. This shouldn't happen but
304
- -- we observed this in some real-world scenarios. This is likely a
305
- -- bug in another place, investigate.
306
- -- ... this should be fixed now, since we use `codeContract` instead
307
- -- of `contract` for everything; it may be safe to remove this check.
308
- when (pc < VMut. length vec) $
309
- VMut. read vec pc >>= \ case
310
- (_, depths, results) | depth < 64 && not (depths `testBit` depth) -> do
311
- VMut. write vec pc (opIx, depths `setBit` depth, results `setBit` fromEnum Stop )
312
- writeIORef covContextRef (True , Just (vec, pc))
313
- _ ->
314
- modifyIORef' covContextRef $ \ (new, _) -> (new, Just (vec, pc))
315
-
316
- -- | Get the VM's current execution location
317
- currentCovLoc vm = (vm. state. pc, fromMaybe 0 $ vmOpIx vm, length vm. frames)
318
-
319
- -- | Get the current contract being executed
320
- currentContract vm = fromMaybe (error " no contract information on coverage" ) $
321
- vm ^? # env % # contracts % at vm. state. codeContract % _Just
281
+ -- | Repeatedly exec a step and add coverage until we have an end result
282
+ loop :: VM Concrete RealWorld -> IO (VMResult Concrete RealWorld , VM Concrete RealWorld )
283
+ loop ! vm = case vm. result of
284
+ Nothing -> do
285
+ addCoverage vm
286
+ stepVM vm >>= loop
287
+ Just r -> pure (r, vm)
288
+
289
+ -- | Execute one instruction on the EVM
290
+ stepVM :: VM Concrete RealWorld -> IO (VM Concrete RealWorld )
291
+ stepVM = stToIO . execStateT exec1
292
+
293
+ -- | Add current location to the CoverageMap
294
+ addCoverage :: VM Concrete RealWorld -> IO ()
295
+ addCoverage ! vm = do
296
+ let (pc, opIx, depth) = currentCovLoc vm
297
+ contract = currentContract vm
298
+
299
+ maybeCovVec <- lookupUsingCodehashOrInsert env. codehashMap contract env. dapp env. coverageRef $ createCoverageVec contract
300
+
301
+ case maybeCovVec of
302
+ Nothing -> pure ()
303
+ Just vec ->
304
+ -- TODO: no-op when pc is out-of-bounds. This shouldn't happen but
305
+ -- we observed this in some real-world scenarios. This is likely a
306
+ -- bug in another place, investigate.
307
+ -- ... this should be fixed now, since we use `codeContract` instead
308
+ -- of `contract` for everything; it may be safe to remove this check.
309
+ when (pc < VMut. length vec) $
310
+ VMut. read vec pc >>= \ case
311
+ (_, depths, results) | depth < 64 && not (depths `testBit` depth) -> do
312
+ VMut. write vec pc (opIx, depths `setBit` depth, results `setBit` fromEnum Stop )
313
+ writeIORef covContextRef (True , Just (vec, pc))
314
+ _ ->
315
+ modifyIORef' covContextRef $ \ (new, _) -> (new, Just (vec, pc))
316
+
317
+ createCoverageVec contract = do
318
+ let size = BS. length . forceBuf . fromJust . view bytecode $ contract
319
+ if size == 0 then pure Nothing else do
320
+ -- IO for making a new vec
321
+ vec <- VMut. new size
322
+ -- We use -1 for opIx to indicate that the location was not covered
323
+ forM_ [0 .. size- 1 ] $ \ i -> VMut. write vec i (- 1 , 0 , 0 )
324
+ pure $ Just vec
325
+
326
+ -- | Get the VM's current execution location
327
+ currentCovLoc vm = (vm. state. pc, fromMaybe 0 $ vmOpIx vm, length vm. frames)
328
+
329
+ -- | Get the current contract being executed
330
+ currentContract vm = fromMaybe (error " no contract information on coverage" ) $
331
+ vm ^? # env % # contracts % at vm. state. codeContract % _Just
322
332
323
333
initialVM :: Bool -> ST s (VM Concrete s )
324
334
initialVM ffi = do
0 commit comments