{-# Language DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
module EVM.Format where
import Prelude hiding (Word)
import EVM (VM, cheatCode, traceForest, traceData, Error (..))
import EVM (Trace, TraceData (..), Log (..), Query (..), FrameContext (..))
import EVM.Dapp (DappInfo, dappSolcByHash, dappSolcByName, showTraceLocation, dappEventMap)
import EVM.Concrete (Word (..), wordValue)
import EVM.Symbolic (maybeLitWord, len)
import EVM.Types (W256 (..), num, Buffer(..))
import EVM.ABI (AbiValue (..), Event (..), AbiType (..))
import EVM.ABI (Indexed (NotIndexed), getAbiSeq, getAbi)
import EVM.ABI (parseTypeName)
import EVM.Solidity (SolcContract, contractName, abiMap)
import EVM.Solidity (methodOutput, methodSignature, methodName)
import Control.Arrow ((>>>))
import Control.Lens (view, preview, ix, _2, to, _Just)
import Data.Binary.Get (runGetOrFail)
import Data.ByteString (ByteString)
import Data.ByteString.Builder (byteStringHex, toLazyByteString)
import Data.ByteString.Lazy (toStrict, fromStrict)
import Data.DoubleWord (signedWord)
import Data.Foldable (toList)
import Data.Maybe (catMaybes, fromMaybe)
import Data.Monoid ((<>))
import Data.Text (Text, pack, unpack, intercalate)
import Data.Text (dropEnd, splitOn)
import Data.Text.Encoding (decodeUtf8, decodeUtf8')
import Data.Tree.View (showTree)
import Data.Vector (Vector, fromList)
import Numeric (showHex)
import qualified Data.ByteString as BS
import qualified Data.Char as Char
import qualified Data.Map as Map
import qualified Data.Text as Text
data Signedness = Signed | Unsigned
deriving (Show)
showDec :: Signedness -> W256 -> Text
showDec signed (W256 w) =
let
i = case signed of
Signed -> num (signedWord w)
Unsigned -> num w
in
if i == num cheatCode
then "<hevm cheat address>"
else if (i :: Integer) == 2 ^ (256 :: Integer) - 1
then "MAX_UINT256"
else Text.pack (show (i :: Integer))
showWordExact :: Word -> Text
showWordExact (C _ (W256 w)) = humanizeInteger w
showWordExplanation :: W256 -> DappInfo -> Text
showWordExplanation w _ | w > 0xffffffff = showDec Unsigned w
showWordExplanation w dapp =
let
fullAbiMap =
mconcat (map (view abiMap) (Map.elems (view dappSolcByName dapp)))
in
case Map.lookup (fromIntegral w) fullAbiMap of
Nothing -> showDec Unsigned w
Just x -> "keccak(\"" <> view methodSignature x <> "\")"
humanizeInteger :: (Num a, Integral a, Show a) => a -> Text
humanizeInteger =
Text.intercalate ","
. reverse
. map Text.reverse
. Text.chunksOf 3
. Text.reverse
. Text.pack
. show
showAbiValues :: Vector AbiValue -> Text
showAbiValues vs =
"(" <> intercalate ", " (toList (fmap showAbiValue vs)) <> ")"
showAbiArray :: Vector AbiValue -> Text
showAbiArray vs =
"[" <> intercalate ", " (toList (fmap showAbiValue vs)) <> "]"
showAbiValue :: AbiValue -> Text
showAbiValue (AbiUInt _ w) =
pack $ show w
showAbiValue (AbiInt _ w) =
pack $ show w
showAbiValue (AbiBool b) =
pack $ show b
showAbiValue (AbiAddress w160) =
pack $ "0x" ++ (showHex w160 "")
showAbiValue (AbiBytes _ bs) =
formatBytes bs
showAbiValue (AbiBytesDynamic bs) =
formatBinary bs
showAbiValue (AbiString bs) =
formatQString bs
showAbiValue (AbiArray _ _ xs) =
showAbiArray xs
showAbiValue (AbiArrayDynamic _ xs) =
showAbiArray xs
showAbiValue (AbiTuple v) =
showAbiValues v
isPrintable :: ByteString -> Bool
isPrintable =
decodeUtf8' >>>
either (const False)
(Text.all (not . Char.isControl))
formatBytes :: ByteString -> Text
formatBytes b =
let (s, _) = BS.spanEnd (== 0) b
in
if isPrintable s
then formatQString s
else formatBinary b
formatSBytes :: Buffer -> Text
formatSBytes (SymbolicBuffer b) = "<" <> pack (show (length b)) <> " symbolic bytes>"
formatSBytes (ConcreteBuffer b) = formatBytes b
formatQString :: ByteString -> Text
formatQString = pack . show
formatString :: ByteString -> Text
formatString bs = decodeUtf8 (fst (BS.spanEnd (== 0) bs))
formatSString :: Buffer -> Text
formatSString (SymbolicBuffer bs) = "<" <> pack (show (length bs)) <> " symbolic bytes (string)>"
formatSString (ConcreteBuffer bs) = formatString bs
formatBinary :: ByteString -> Text
formatBinary =
(<>) "0x" . decodeUtf8 . toStrict . toLazyByteString . byteStringHex
formatSBinary :: Buffer -> Text
formatSBinary (SymbolicBuffer bs) = "<" <> pack (show (length bs)) <> " symbolic bytes>"
formatSBinary (ConcreteBuffer bs) = formatBinary bs
showTraceTree :: DappInfo -> VM -> Text
showTraceTree dapp =
traceForest
>>> fmap (fmap (unpack . showTrace dapp))
>>> concatMap showTree
>>> pack
showTrace :: DappInfo -> Trace -> Text
showTrace dapp trace =
let
pos =
case showTraceLocation dapp trace of
Left x -> " \x1b[90m" <> x <> "\x1b[0m"
Right x -> " \x1b[90m(" <> x <> ")\x1b[0m"
fullAbiMap =
mconcat (map (view abiMap) (Map.elems (view dappSolcByName dapp)))
in case view traceData trace of
EventTrace (Log _ bytes topics) ->
case topics of
[] ->
mconcat
[ "\x1b[36m"
, "log0("
, formatSBinary bytes
, ")"
, "\x1b[0m"
] <> pos
(topic:_) ->
let unknownTopic =
mconcat
[ "\x1b[36m"
, "log" <> (pack (show (length topics))) <> "("
, formatSBinary bytes <> ", "
, intercalate ", " (map (pack . show) topics) <> ")"
, "\x1b[0m"
] <> pos
in case maybeLitWord topic of
Just top -> case Map.lookup (wordValue top) (view dappEventMap dapp) of
Just (Event name _ types) ->
mconcat
[ "\x1b[36m"
, name
, showValues [t | (t, NotIndexed) <- types] bytes
, "\x1b[0m"
] <> pos
Nothing -> unknownTopic
Nothing -> unknownTopic
QueryTrace q ->
case q of
PleaseFetchContract addr _ ->
"fetch contract " <> pack (show addr) <> pos
PleaseFetchSlot addr slot _ ->
"fetch storage slot " <> pack (show slot) <> " from " <> pack (show addr) <> pos
PleaseAskSMT _ _ _ ->
"ask smt" <> pos
ErrorTrace e ->
case e of
Revert out ->
"\x1b[91merror\x1b[0m " <> "Revert " <> showError out <> pos
_ ->
"\x1b[91merror\x1b[0m " <> pack (show e) <> pos
ReturnTrace out (CallContext _ _ _ _ hash (Just abi) _ _ _) ->
case getAbiMethodOutput dapp hash abi of
Nothing ->
"← " <>
case Map.lookup (fromIntegral abi) fullAbiMap of
Just m ->
case (view methodOutput m) of
Just (_, t) ->
pack (show t) <> " " <> showValue t out
Nothing ->
formatSBinary out
Nothing ->
formatSBinary out
Just (_, t) ->
"← " <> pack (show t) <> " " <> showValue t out
ReturnTrace out (CallContext {}) ->
"← " <> formatSBinary out
ReturnTrace out (CreationContext {}) ->
"← " <> pack (show (len out)) <> " bytes of code"
EntryTrace t ->
t
FrameTrace (CreationContext hash _ _ ) ->
"create " <> maybeContractName (preview (dappSolcByHash . ix hash . _2) dapp) <> pos
FrameTrace (CallContext target context _ _ hash abi calldata _ _) ->
let calltype = if target == context
then "call "
else "delegatecall "
in case preview (dappSolcByHash . ix hash . _2) dapp of
Nothing ->
calltype
<> pack (show target)
<> pack "::"
<> case Map.lookup (fromIntegral (fromMaybe 0x00 abi)) fullAbiMap of
Just m ->
view methodName m
<> showCall (catMaybes (getAbiTypes (view methodSignature m))) calldata
Nothing ->
formatSBinary calldata
<> pos
Just solc ->
calltype
<> "\x1b[1m"
<> view (contractName . to contractNamePart) solc
<> "::"
<> maybe "[fallback function]"
(fromMaybe "[unknown method]" . maybeAbiName solc)
abi
<> maybe ("(" <> formatSBinary calldata <> ")")
(\x -> showCall (catMaybes x) calldata)
(abi >>= fmap getAbiTypes . maybeAbiName solc)
<> "\x1b[0m"
<> pos
getAbiMethodOutput
:: DappInfo -> W256 -> Word -> Maybe (Text, AbiType)
getAbiMethodOutput dapp hash abi =
preview
( dappSolcByHash . ix hash . _2 . abiMap
. ix (fromIntegral abi) . methodOutput . _Just
)
dapp
getAbiTypes :: Text -> [Maybe AbiType]
getAbiTypes abi = map (parseTypeName mempty) types
where
types =
filter (/= "") $
splitOn "," (dropEnd 1 (last (splitOn "(" abi)))
showCall :: [AbiType] -> Buffer -> Text
showCall ts (SymbolicBuffer bs) = showValues ts $ SymbolicBuffer (drop 4 bs)
showCall ts (ConcreteBuffer bs) = showValues ts $ ConcreteBuffer (BS.drop 4 bs)
showError :: ByteString -> Text
showError bs = case BS.take 4 bs of
"\b\195y\160" -> showCall [AbiStringType] (ConcreteBuffer bs)
_ -> formatBinary bs
showValues :: [AbiType] -> Buffer -> Text
showValues ts (SymbolicBuffer _) = "symbolic: " <> (pack . show $ AbiTupleType (fromList ts))
showValues ts (ConcreteBuffer bs) =
case runGetOrFail (getAbiSeq (length ts) ts) (fromStrict bs) of
Right (_, _, xs) -> showAbiValues xs
Left (_, _, _) -> formatBinary bs
showValue :: AbiType -> Buffer -> Text
showValue t (SymbolicBuffer _) = "symbolic: " <> (pack $ show t)
showValue t (ConcreteBuffer bs) =
case runGetOrFail (getAbi t) (fromStrict bs) of
Right (_, _, x) -> showAbiValue x
Left (_, _, _) -> formatBinary bs
maybeContractName :: Maybe SolcContract -> Text
maybeContractName =
maybe "<unknown contract>" (view (contractName . to contractNamePart))
maybeAbiName :: SolcContract -> Word -> Maybe Text
maybeAbiName solc abi = preview (abiMap . ix (fromIntegral abi) . methodSignature) solc
contractNamePart :: Text -> Text
contractNamePart x = Text.split (== ':') x !! 1
contractPathPart :: Text -> Text
contractPathPart x = Text.split (== ':') x !! 0