@@ -93,121 +93,150 @@ execTxWith executeTx tx = do
9393 gasLeftBeforeTx <- gets (. state. gas)
9494 vmResult <- runFully
9595 gasLeftAfterTx <- gets (. state. gas)
96- handleErrorsAndConstruction vmResult vmBeforeTx
96+ handleErrors vmResult vmBeforeTx
97+ when isCreate $ handleConstruction vmResult tx. dst
9798 pure (vmResult, gasLeftBeforeTx - gasLeftAfterTx)
9899 where
99- runFully = do
100- config <- asks (. cfg)
101- -- TODO: Is the latest block a good default? It makes fuzzing hard to reproduce. Rethink this.
102- let rpcBlock = maybe EVM.Fetch. Latest (EVM.Fetch. BlockNumber . fromIntegral ) config. rpcBlock
100+ isCreate = case tx. call of
101+ (SolCreate _) -> True
102+ _ -> False
103103
104+ runFully = do
104105 vmResult <- executeTx
105- -- For queries, we halt execution because the VM needs some additional
106- -- information from the outside. We provide this information and resume
107- -- the execution by recursively calling `runFully`.
108- case getQuery vmResult of
109- -- A previously unknown contract is required
110- Just q@ (PleaseFetchContract addr _ continuation) -> do
111- cacheRef <- asks (. fetchContractCache)
112- cache <- liftIO $ readIORef cacheRef
113- case Map. lookup addr cache of
114- Just (Just contract) -> fromEVM (continuation contract)
115- Just Nothing -> do
116- v <- get
117- v' <- liftIO $ stToIO $ execStateT (continuation emptyAccount) v
118- put v'
119- Nothing -> do
120- logMsg $ " INFO: Performing RPC: " <> show q
121- case config. rpcUrl of
122- Just rpcUrl -> do
123- ret <- liftIO $ safeFetchContractFrom rpcBlock rpcUrl addr
124- case ret of
125- -- TODO: fix hevm to not return an empty contract in case of an error
126- Just contract | contract. code /= RuntimeCode (ConcreteRuntimeCode " " ) -> do
127- fromEVM (continuation contract)
128- liftIO $ atomicWriteIORef cacheRef $ Map. insert addr (Just contract) cache
129- _ -> do
130- -- TODO: better error reporting in HEVM, when intermmittent
131- -- network error then retry
132- liftIO $ atomicWriteIORef cacheRef $ Map. insert addr Nothing cache
133- logMsg $ " ERROR: Failed to fetch contract: " <> show q
134- -- TODO: How should we fail here? It could be a network error,
135- -- RPC server returning junk etc.
136- fromEVM (continuation emptyAccount)
137- Nothing -> do
138- liftIO $ atomicWriteIORef cacheRef $ Map. insert addr Nothing cache
139- logMsg $ " ERROR: Requested RPC but it is not configured: " <> show q
140- -- TODO: How should we fail here? RPC is not configured but VM
141- -- wants to fetch
142- fromEVM (continuation emptyAccount)
143- runFully -- resume execution
144-
145- -- A previously unknown slot is required
146- Just q@ (PleaseFetchSlot addr slot continuation) -> do
147- cacheRef <- asks (. fetchSlotCache)
148- cache <- liftIO $ readIORef cacheRef
149- case Map. lookup addr cache >>= Map. lookup slot of
150- Just (Just value) -> fromEVM (continuation value)
151- Just Nothing -> fromEVM (continuation 0 )
152- Nothing -> do
153- logMsg $ " INFO: Performing RPC: " <> show q
154- case config. rpcUrl of
155- Just rpcUrl -> do
156- ret <- liftIO $ safeFetchSlotFrom rpcBlock rpcUrl addr slot
157- case ret of
158- Just value -> do
159- fromEVM (continuation value)
160- liftIO $ atomicWriteIORef cacheRef $
161- Map. insertWith Map. union addr (Map. singleton slot (Just value)) cache
162- Nothing -> do
163- -- TODO: How should we fail here? It could be a network error,
164- -- RPC server returning junk etc.
165- logMsg $ " ERROR: Failed to fetch slot: " <> show q
166- liftIO $ atomicWriteIORef cacheRef $
167- Map. insertWith Map. union addr (Map. singleton slot Nothing ) cache
168- fromEVM (continuation 0 )
169- Nothing -> do
170- logMsg $ " ERROR: Requested RPC but it is not configured: " <> show q
171- -- Use the zero slot
172- fromEVM (continuation 0 )
173- runFully -- resume execution
174-
175- -- Execute a FFI call
176- Just (PleaseDoFFI (cmd : args) continuation) -> do
177- (_, stdout, _) <- liftIO $ readProcessWithExitCode cmd args " "
178- let encodedResponse = encodeAbiValue $
179- AbiTuple (V. fromList [AbiBytesDynamic . hexText . T. pack $ stdout])
180- fromEVM (continuation encodedResponse)
181- runFully
182-
183- -- No queries to answer, the tx is fully executed and the result is final
184- _ -> pure vmResult
185-
186- -- | Handles reverts, failures and contract creations that might be the result
187- -- (`vmResult`) of executing transaction `tx`.
188- handleErrorsAndConstruction vmResult vmBeforeTx = case (vmResult, tx. call) of
189- (Reversion , _) -> do
190- tracesBeforeVMReset <- gets (. traces)
191- codeContractBeforeVMReset <- gets (. state. codeContract)
192- calldataBeforeVMReset <- gets (. state. calldata)
193- callvalueBeforeVMReset <- gets (. state. callvalue)
194- -- If a transaction reverts reset VM to state before the transaction.
195- put vmBeforeTx
196- -- Undo reset of some of the VM state.
197- -- Otherwise we'd loose all information about the reverted transaction like
198- -- contract address, calldata, result and traces.
199- # result ?= vmResult
200- # state % # calldata .= calldataBeforeVMReset
201- # state % # callvalue .= callvalueBeforeVMReset
202- # traces .= tracesBeforeVMReset
203- # state % # codeContract .= codeContractBeforeVMReset
204- (VMFailure x, _) -> vmExcept x
205- (VMSuccess (ConcreteBuf bytecode'), SolCreate _) -> do
206- -- Handle contract creation.
207- # env % # contracts % at (LitAddr tx. dst) % _Just % # code .= InitCode mempty mempty
208- fromEVM $ replaceCodeOfSelf (RuntimeCode (ConcreteRuntimeCode bytecode'))
209- modify' $ execState $ loadContract (LitAddr tx. dst)
210- _ -> pure ()
106+ maybe (pure vmResult) (\ q -> handleQuery q >> runFully) (getQuery vmResult)
107+
108+ getRpcInfo
109+ :: (MonadReader Env m )
110+ => m (Maybe T. Text , EVM.Fetch. BlockNumber )
111+ getRpcInfo = do
112+ config <- asks (. cfg)
113+ -- TODO: Is the latest block a good default? It makes fuzzing hard to reproduce. Rethink this.
114+ let rpcBlock = maybe EVM.Fetch. Latest (EVM.Fetch. BlockNumber . fromIntegral ) config. rpcBlock
115+ return (config. rpcUrl, rpcBlock)
116+
117+ handleQuery
118+ :: (MonadIO m , MonadState (VM Concrete RealWorld ) m , MonadReader Env m , MonadThrow m )
119+ => Query Concrete RealWorld
120+ -> m ()
121+
122+ -- For queries, we halt execution because the VM needs some additional
123+ -- information from the outside. We provide this information and resume
124+ -- the execution by recursively calling `runFully`.
125+ handleQuery q@ (PleaseFetchContract addr _ continuation) = do
126+ -- A previously unknown contract is required
127+ cacheRef <- asks (. fetchContractCache)
128+ cache <- liftIO $ readIORef cacheRef
129+ case Map. lookup addr cache of
130+ Just (Just contract) -> fromEVM (continuation contract)
131+ Just Nothing -> do
132+ v <- get
133+ v' <- liftIO $ stToIO $ execStateT (continuation emptyAccount) v
134+ put v'
135+ Nothing -> do
136+ logMsg $ " INFO: Performing RPC: " <> show q
137+ (maybeRpcUrl, rpcBlock) <- getRpcInfo
138+ case maybeRpcUrl of
139+ Just rpcUrl -> do
140+ ret <- liftIO $ safeFetchContractFrom rpcBlock rpcUrl addr
141+ case ret of
142+ -- TODO: fix hevm to not return an empty contract in case of an error
143+ Just contract | contract. code /= RuntimeCode (ConcreteRuntimeCode " " ) -> do
144+ fromEVM (continuation contract)
145+ liftIO $ atomicWriteIORef cacheRef $ Map. insert addr (Just contract) cache
146+ _ -> do
147+ -- TODO: better error reporting in HEVM, when intermmittent
148+ -- network error then retry
149+ liftIO $ atomicWriteIORef cacheRef $ Map. insert addr Nothing cache
150+ logMsg $ " ERROR: Failed to fetch contract: " <> show q
151+ -- TODO: How should we fail here? It could be a network error,
152+ -- RPC server returning junk etc.
153+ fromEVM (continuation emptyAccount)
154+ Nothing -> do
155+ liftIO $ atomicWriteIORef cacheRef $ Map. insert addr Nothing cache
156+ logMsg $ " ERROR: Requested RPC but it is not configured: " <> show q
157+ -- TODO: How should we fail here? RPC is not configured but VM
158+ -- wants to fetch
159+ fromEVM (continuation emptyAccount)
160+
161+ -- A previously unknown slot is required
162+ handleQuery q@ (PleaseFetchSlot addr slot continuation) = do
163+ cacheRef <- asks (. fetchSlotCache)
164+ cache <- liftIO $ readIORef cacheRef
165+ case Map. lookup addr cache >>= Map. lookup slot of
166+ Just (Just value) -> fromEVM (continuation value)
167+ Just Nothing -> fromEVM (continuation 0 )
168+ Nothing -> do
169+ logMsg $ " INFO: Performing RPC: " <> show q
170+ (maybeRpcUrl, rpcBlock) <- getRpcInfo
171+ case maybeRpcUrl of
172+ Just rpcUrl -> do
173+ ret <- liftIO $ safeFetchSlotFrom rpcBlock rpcUrl addr slot
174+ case ret of
175+ Just value -> do
176+ fromEVM (continuation value)
177+ liftIO $ atomicWriteIORef cacheRef $
178+ Map. insertWith Map. union addr (Map. singleton slot (Just value)) cache
179+ Nothing -> do
180+ -- TODO: How should we fail here? It could be a network error,
181+ -- RPC server returning junk etc.
182+ logMsg $ " ERROR: Failed to fetch slot: " <> show q
183+ liftIO $ atomicWriteIORef cacheRef $
184+ Map. insertWith Map. union addr (Map. singleton slot Nothing ) cache
185+ fromEVM (continuation 0 )
186+ Nothing -> do
187+ logMsg $ " ERROR: Requested RPC but it is not configured: " <> show q
188+ -- Use the zero slot
189+ fromEVM (continuation 0 )
190+
191+ -- Execute a FFI call
192+ handleQuery (PleaseDoFFI (cmd : args) continuation) = do
193+ (_, stdout, _) <- liftIO $ readProcessWithExitCode cmd args " "
194+ let encodedResponse = encodeAbiValue $
195+ AbiTuple (V. fromList [AbiBytesDynamic . hexText . T. pack $ stdout])
196+ fromEVM (continuation encodedResponse)
197+
198+ handleQuery (PleaseDoFFI [] _) = error " Malformed FFI call"
199+
200+ handleErrors
201+ :: (MonadIO m , MonadState (VM Concrete RealWorld ) m , MonadReader Env m , MonadThrow m )
202+ => VMResult Concrete RealWorld
203+ -> VM Concrete RealWorld
204+ -> m ()
205+
206+ -- | Handles reverts, failures and contract creations that might be the result
207+ -- (`vmResult`) of executing transaction `tx`.
208+ handleErrors vmResult@ Reversion vmBeforeTx = do
209+ tracesBeforeVMReset <- gets (. traces)
210+ codeContractBeforeVMReset <- gets (. state. codeContract)
211+ calldataBeforeVMReset <- gets (. state. calldata)
212+ callvalueBeforeVMReset <- gets (. state. callvalue)
213+ -- If a transaction reverts reset VM to state before the transaction.
214+ put vmBeforeTx
215+ -- Undo reset of some of the VM state.
216+ -- Otherwise we'd loose all information about the reverted transaction like
217+ -- contract address, calldata, result and traces.
218+ # result ?= vmResult
219+ # state % # calldata .= calldataBeforeVMReset
220+ # state % # callvalue .= callvalueBeforeVMReset
221+ # traces .= tracesBeforeVMReset
222+ # state % # codeContract .= codeContractBeforeVMReset
223+ handleErrors (VMFailure x) _ = vmExcept x
224+ handleErrors _ _ = pure ()
225+
226+ handleConstruction
227+ :: (MonadIO m , MonadState (VM Concrete RealWorld ) m , MonadReader Env m , MonadThrow m )
228+ => VMResult Concrete s
229+ -> Addr
230+ -> m ()
231+
232+ -- | Handles reverts, failures and contract creations that might be the result
233+ -- (`vmResult`) of executing transaction `tx`.
234+ handleConstruction (VMSuccess (ConcreteBuf bytecode')) dst = do
235+ -- Handle contract creation.
236+ # env % # contracts % at (LitAddr dst) % _Just % # code .= InitCode mempty mempty
237+ fromEVM $ replaceCodeOfSelf (RuntimeCode (ConcreteRuntimeCode bytecode'))
238+ modify' $ execState $ loadContract (LitAddr dst)
239+ handleConstruction _ _ = pure ()
211240
212241logMsg :: (MonadIO m , MonadReader Env m ) => String -> m ()
213242logMsg msg = do
0 commit comments