{-# Language TemplateHaskell #-}
module EVM.Dapp where
import EVM (Trace, traceContract, traceOpIx, ContractCode(..), Contract(..), codehash, contractcode, RuntimeCode (..))
import EVM.ABI (Event, AbiType, SolError)
import EVM.Debug (srcMapCodePos)
import EVM.Solidity
import EVM.Types (W256, abiKeccak, keccak', Addr, regexMatches, unlit, unlitByte)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Aeson (Value)
import Data.Bifunctor (first)
import Data.Text (Text, isPrefixOf, pack, unpack)
import Data.Text.Encoding (encodeUtf8)
import Data.Map (Map, toList, elems)
import Data.List (sort)
import Data.Maybe (isJust, fromJust, mapMaybe)
import Data.Word (Word32)
import EVM.Concrete
import Control.Arrow ((>>>))
import Control.Lens
import Data.List (find)
import qualified Data.Map as Map
import qualified Data.Vector as V
data DappInfo = DappInfo
{ DappInfo -> FilePath
_dappRoot :: FilePath
, DappInfo -> Map Text SolcContract
_dappSolcByName :: Map Text SolcContract
, DappInfo -> Map W256 (CodeType, SolcContract)
_dappSolcByHash :: Map W256 (CodeType, SolcContract)
, DappInfo -> [(Code, SolcContract)]
_dappSolcByCode :: [(Code, SolcContract)]
, DappInfo -> SourceCache
_dappSources :: SourceCache
, DappInfo -> [(Text, [(Test, [AbiType])])]
_dappUnitTests :: [(Text, [(Test, [AbiType])])]
, DappInfo -> Map Word32 Method
_dappAbiMap :: Map Word32 Method
, DappInfo -> Map W256 Event
_dappEventMap :: Map W256 Event
, DappInfo -> Map W256 SolError
_dappErrorMap :: Map W256 SolError
, DappInfo -> Map Int Value
_dappAstIdMap :: Map Int Value
, DappInfo -> SrcMap -> Maybe Value
_dappAstSrcMap :: SrcMap -> Maybe Value
}
data Code =
Code {
Code -> ByteString
raw :: ByteString,
Code -> [Reference]
immutableLocations :: [Reference]
}
deriving Int -> Code -> ShowS
[Code] -> ShowS
Code -> FilePath
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
showList :: [Code] -> ShowS
$cshowList :: [Code] -> ShowS
show :: Code -> FilePath
$cshow :: Code -> FilePath
showsPrec :: Int -> Code -> ShowS
$cshowsPrec :: Int -> Code -> ShowS
Show
data DappContext = DappContext
{ DappContext -> DappInfo
_contextInfo :: DappInfo
, DappContext -> Map Addr Contract
_contextEnv :: Map Addr Contract
}
data Test = ConcreteTest Text | SymbolicTest Text | InvariantTest Text
makeLenses ''DappInfo
makeLenses ''DappContext
instance Show Test where
show :: Test -> FilePath
show Test
t = Text -> FilePath
unpack forall a b. (a -> b) -> a -> b
$ Test -> Text
extractSig Test
t
dappInfo
:: FilePath -> Map Text SolcContract -> SourceCache -> DappInfo
dappInfo :: FilePath -> Map Text SolcContract -> SourceCache -> DappInfo
dappInfo FilePath
root Map Text SolcContract
solcByName SourceCache
sources =
let
solcs :: [SolcContract]
solcs = forall k a. Map k a -> [a]
Map.elems Map Text SolcContract
solcByName
astIds :: Map Int Value
astIds = forall (f :: * -> *). Foldable f => f Value -> Map Int Value
astIdMap forall a b. (a -> b) -> a -> b
$ forall a b. (a, b) -> b
snd forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall k a. Map k a -> [(k, a)]
toList (forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' SourceCache (Map Text Value)
sourceAsts SourceCache
sources)
immutables :: [SolcContract]
immutables = forall a. (a -> Bool) -> [a] -> [a]
filter (forall a. Eq a => a -> a -> Bool
(/=) forall a. Monoid a => a
mempty forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' SolcContract (Map W256 [Reference])
immutableReferences)) [SolcContract]
solcs
in DappInfo
{ _dappRoot :: FilePath
_dappRoot = FilePath
root
, _dappUnitTests :: [(Text, [(Test, [AbiType])])]
_dappUnitTests = [SolcContract] -> [(Text, [(Test, [AbiType])])]
findAllUnitTests [SolcContract]
solcs
, _dappSources :: SourceCache
_dappSources = SourceCache
sources
, _dappSolcByName :: Map Text SolcContract
_dappSolcByName = Map Text SolcContract
solcByName
, _dappSolcByHash :: Map W256 (CodeType, SolcContract)
_dappSolcByHash =
let
f :: Getting W256 SolcContract W256
-> CodeType -> Map W256 (CodeType, SolcContract)
f Getting W256 SolcContract W256
g CodeType
k = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [(forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Getting W256 SolcContract W256
g SolcContract
x, (CodeType
k, SolcContract
x)) | SolcContract
x <- [SolcContract]
solcs]
in
forall a. Monoid a => a -> a -> a
mappend
(Getting W256 SolcContract W256
-> CodeType -> Map W256 (CodeType, SolcContract)
f Lens' SolcContract W256
runtimeCodehash CodeType
Runtime)
(Getting W256 SolcContract W256
-> CodeType -> Map W256 (CodeType, SolcContract)
f Lens' SolcContract W256
creationCodehash CodeType
Creation)
, _dappSolcByCode :: [(Code, SolcContract)]
_dappSolcByCode =
[(ByteString -> [Reference] -> Code
Code (SolcContract -> ByteString
_runtimeCode SolcContract
x) (forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ forall k a. Map k a -> [a]
elems forall a b. (a -> b) -> a -> b
$ SolcContract -> Map W256 [Reference]
_immutableReferences SolcContract
x), SolcContract
x) | SolcContract
x <- [SolcContract]
immutables]
, _dappAbiMap :: Map Word32 Method
_dappAbiMap = forall a. Monoid a => [a] -> a
mconcat (forall a b. (a -> b) -> [a] -> [b]
map (forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' SolcContract (Map Word32 Method)
abiMap) [SolcContract]
solcs)
, _dappEventMap :: Map W256 Event
_dappEventMap = forall a. Monoid a => [a] -> a
mconcat (forall a b. (a -> b) -> [a] -> [b]
map (forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' SolcContract (Map W256 Event)
eventMap) [SolcContract]
solcs)
, _dappErrorMap :: Map W256 SolError
_dappErrorMap = forall a. Monoid a => [a] -> a
mconcat (forall a b. (a -> b) -> [a] -> [b]
map (forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' SolcContract (Map W256 SolError)
errorMap) [SolcContract]
solcs)
, _dappAstIdMap :: Map Int Value
_dappAstIdMap = Map Int Value
astIds
, _dappAstSrcMap :: SrcMap -> Maybe Value
_dappAstSrcMap = Map Int Value -> SrcMap -> Maybe Value
astSrcMap Map Int Value
astIds
}
emptyDapp :: DappInfo
emptyDapp :: DappInfo
emptyDapp = FilePath -> Map Text SolcContract -> SourceCache -> DappInfo
dappInfo FilePath
"" forall a. Monoid a => a
mempty ([(Text, ByteString)]
-> [Vector ByteString] -> Map Text Value -> SourceCache
SourceCache forall a. Monoid a => a
mempty forall a. Monoid a => a
mempty forall a. Monoid a => a
mempty)
unitTestMarkerAbi :: Word32
unitTestMarkerAbi :: Word32
unitTestMarkerAbi = ByteString -> Word32
abiKeccak (Text -> ByteString
encodeUtf8 Text
"IS_TEST()")
findAllUnitTests :: [SolcContract] -> [(Text, [(Test, [AbiType])])]
findAllUnitTests :: [SolcContract] -> [(Text, [(Test, [AbiType])])]
findAllUnitTests = Text -> [SolcContract] -> [(Text, [(Test, [AbiType])])]
findUnitTests Text
".*:.*\\.(test|prove|invariant).*"
mkTest :: Text -> Maybe Test
mkTest :: Text -> Maybe Test
mkTest Text
sig
| Text
"test" Text -> Text -> Bool
`isPrefixOf` Text
sig = forall a. a -> Maybe a
Just (Text -> Test
ConcreteTest Text
sig)
| Text
"prove" Text -> Text -> Bool
`isPrefixOf` Text
sig = forall a. a -> Maybe a
Just (Text -> Test
SymbolicTest Text
sig)
| Text
"invariant" Text -> Text -> Bool
`isPrefixOf` Text
sig = forall a. a -> Maybe a
Just (Text -> Test
InvariantTest Text
sig)
| Bool
otherwise = forall a. Maybe a
Nothing
findUnitTests :: Text -> ([SolcContract] -> [(Text, [(Test, [AbiType])])])
findUnitTests :: Text -> [SolcContract] -> [(Text, [(Test, [AbiType])])]
findUnitTests Text
match =
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap forall a b. (a -> b) -> a -> b
$ \SolcContract
c ->
case forall s (m :: * -> *) a.
MonadReader s m =>
Getting (First a) s a -> m (Maybe a)
preview (Lens' SolcContract (Map Word32 Method)
abiMap forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall m. Ixed m => Index m -> Traversal' m (IxValue m)
ix Word32
unitTestMarkerAbi) SolcContract
c of
Maybe Method
Nothing -> []
Just Method
_ ->
let testNames :: [(Test, [AbiType])]
testNames = (Text -> Bool) -> SolcContract -> [(Test, [AbiType])]
unitTestMethodsFiltered (Text -> Text -> Bool
regexMatches Text
match) SolcContract
c
in [(forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' SolcContract Text
contractName SolcContract
c, [(Test, [AbiType])]
testNames) | Bool -> Bool
not (ByteString -> Bool
BS.null (forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' SolcContract ByteString
runtimeCode SolcContract
c)) Bool -> Bool -> Bool
&& Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => t a -> Bool
null [(Test, [AbiType])]
testNames)]
unitTestMethodsFiltered :: (Text -> Bool) -> (SolcContract -> [(Test, [AbiType])])
unitTestMethodsFiltered :: (Text -> Bool) -> SolcContract -> [(Test, [AbiType])]
unitTestMethodsFiltered Text -> Bool
matcher SolcContract
c =
let
testName :: (Test, [AbiType]) -> Text
testName (Test, [AbiType])
method = (forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' SolcContract Text
contractName SolcContract
c) forall a. Semigroup a => a -> a -> a
<> Text
"." forall a. Semigroup a => a -> a -> a
<> (Test -> Text
extractSig (forall a b. (a, b) -> a
fst (Test, [AbiType])
method))
in
forall a. (a -> Bool) -> [a] -> [a]
filter (Text -> Bool
matcher forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Test, [AbiType]) -> Text
testName) (SolcContract -> [(Test, [AbiType])]
unitTestMethods SolcContract
c)
unitTestMethods :: SolcContract -> [(Test, [AbiType])]
unitTestMethods :: SolcContract -> [(Test, [AbiType])]
unitTestMethods =
forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' SolcContract (Map Word32 Method)
abiMap
forall {k} (cat :: k -> k -> *) (a :: k) (b :: k) (c :: k).
Category cat =>
cat a b -> cat b c -> cat a c
>>> forall k a. Map k a -> [a]
Map.elems
forall {k} (cat :: k -> k -> *) (a :: k) (b :: k) (c :: k).
Category cat =>
cat a b -> cat b c -> cat a c
>>> forall a b. (a -> b) -> [a] -> [b]
map (\Method
f -> (Text -> Maybe Test
mkTest forall a b. (a -> b) -> a -> b
$ forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' Method Text
methodSignature Method
f, forall a b. (a, b) -> b
snd forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' Method [(Text, AbiType)]
methodInputs Method
f))
forall {k} (cat :: k -> k -> *) (a :: k) (b :: k) (c :: k).
Category cat =>
cat a b -> cat b c -> cat a c
>>> forall a. (a -> Bool) -> [a] -> [a]
filter (forall a. Maybe a -> Bool
isJust forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst)
forall {k} (cat :: k -> k -> *) (a :: k) (b :: k) (c :: k).
Category cat =>
cat a b -> cat b c -> cat a c
>>> forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first forall a. HasCallStack => Maybe a -> a
fromJust)
extractSig :: Test -> Text
(ConcreteTest Text
sig) = Text
sig
extractSig (SymbolicTest Text
sig) = Text
sig
extractSig (InvariantTest Text
sig) = Text
sig
traceSrcMap :: DappInfo -> Trace -> Maybe SrcMap
traceSrcMap :: DappInfo -> Trace -> Maybe SrcMap
traceSrcMap DappInfo
dapp Trace
trace =
let
h :: Contract
h = forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' Trace Contract
traceContract Trace
trace
i :: Int
i = forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' Trace Int
traceOpIx Trace
trace
in DappInfo -> Contract -> Int -> Maybe SrcMap
srcMap DappInfo
dapp Contract
h Int
i
srcMap :: DappInfo -> Contract -> Int -> Maybe SrcMap
srcMap :: DappInfo -> Contract -> Int -> Maybe SrcMap
srcMap DappInfo
dapp Contract
contr Int
opIndex = do
SolcContract
sol <- Contract -> DappInfo -> Maybe SolcContract
findSrc Contract
contr DappInfo
dapp
case forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' Contract ContractCode
contractcode Contract
contr of
(InitCode ByteString
_ Expr 'Buf
_) ->
forall s (m :: * -> *) a.
MonadReader s m =>
Getting (First a) s a -> m (Maybe a)
preview (Lens' SolcContract (Seq SrcMap)
creationSrcmap forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall m. Ixed m => Index m -> Traversal' m (IxValue m)
ix Int
opIndex) SolcContract
sol
(RuntimeCode RuntimeCode
_) ->
forall s (m :: * -> *) a.
MonadReader s m =>
Getting (First a) s a -> m (Maybe a)
preview (Lens' SolcContract (Seq SrcMap)
runtimeSrcmap forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall m. Ixed m => Index m -> Traversal' m (IxValue m)
ix Int
opIndex) SolcContract
sol
findSrc :: Contract -> DappInfo -> Maybe SolcContract
findSrc :: Contract -> DappInfo -> Maybe SolcContract
findSrc Contract
c DappInfo
dapp = do
W256
hash <- Expr 'EWord -> Maybe W256
unlit (forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' Contract (Expr 'EWord)
codehash Contract
c)
case forall s (m :: * -> *) a.
MonadReader s m =>
Getting (First a) s a -> m (Maybe a)
preview (Lens' DappInfo (Map W256 (CodeType, SolcContract))
dappSolcByHash forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall m. Ixed m => Index m -> Traversal' m (IxValue m)
ix W256
hash) DappInfo
dapp of
Just (CodeType
_, SolcContract
v) -> forall a. a -> Maybe a
Just SolcContract
v
Maybe (CodeType, SolcContract)
Nothing -> ContractCode -> DappInfo -> Maybe SolcContract
lookupCode (forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' Contract ContractCode
contractcode Contract
c) DappInfo
dapp
lookupCode :: ContractCode -> DappInfo -> Maybe SolcContract
lookupCode :: ContractCode -> DappInfo -> Maybe SolcContract
lookupCode (InitCode ByteString
c Expr 'Buf
_) DappInfo
a =
forall a b. (a, b) -> b
snd forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall s (m :: * -> *) a.
MonadReader s m =>
Getting (First a) s a -> m (Maybe a)
preview (Lens' DappInfo (Map W256 (CodeType, SolcContract))
dappSolcByHash forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall m. Ixed m => Index m -> Traversal' m (IxValue m)
ix (ByteString -> W256
keccak' (ByteString -> ByteString
stripBytecodeMetadata ByteString
c))) DappInfo
a
lookupCode (RuntimeCode (ConcreteRuntimeCode ByteString
c)) DappInfo
a =
case forall a b. (a, b) -> b
snd forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall s (m :: * -> *) a.
MonadReader s m =>
Getting (First a) s a -> m (Maybe a)
preview (Lens' DappInfo (Map W256 (CodeType, SolcContract))
dappSolcByHash forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall m. Ixed m => Index m -> Traversal' m (IxValue m)
ix (ByteString -> W256
keccak' (ByteString -> ByteString
stripBytecodeMetadata ByteString
c))) DappInfo
a of
Just SolcContract
x -> forall (m :: * -> *) a. Monad m => a -> m a
return SolcContract
x
Maybe SolcContract
Nothing -> forall a b. (a, b) -> b
snd forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (ByteString -> Code -> Bool
compareCode ByteString
c forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst) (forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' DappInfo [(Code, SolcContract)]
dappSolcByCode DappInfo
a)
lookupCode (RuntimeCode (SymbolicRuntimeCode Vector (Expr 'Byte)
c)) DappInfo
a = let
code :: ByteString
code = [Word8] -> ByteString
BS.pack forall a b. (a -> b) -> a -> b
$ forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Expr 'Byte -> Maybe Word8
unlitByte forall a b. (a -> b) -> a -> b
$ forall a. Vector a -> [a]
V.toList Vector (Expr 'Byte)
c
in case forall a b. (a, b) -> b
snd forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall s (m :: * -> *) a.
MonadReader s m =>
Getting (First a) s a -> m (Maybe a)
preview (Lens' DappInfo (Map W256 (CodeType, SolcContract))
dappSolcByHash forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall m. Ixed m => Index m -> Traversal' m (IxValue m)
ix (ByteString -> W256
keccak' (ByteString -> ByteString
stripBytecodeMetadata ByteString
code))) DappInfo
a of
Just SolcContract
x -> forall (m :: * -> *) a. Monad m => a -> m a
return SolcContract
x
Maybe SolcContract
Nothing -> forall a b. (a, b) -> b
snd forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (ByteString -> Code -> Bool
compareCode ByteString
code forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst) (forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' DappInfo [(Code, SolcContract)]
dappSolcByCode DappInfo
a)
compareCode :: ByteString -> Code -> Bool
compareCode :: ByteString -> Code -> Bool
compareCode ByteString
raw (Code ByteString
template [Reference]
locs) =
let holes' :: [(Int, Int)]
holes' = forall a. Ord a => [a] -> [a]
sort [(Int
start, Int
len) | (Reference Int
start Int
len) <- [Reference]
locs]
insert :: a -> Int -> ByteString -> ByteString
insert a
at' Int
len' ByteString
bs = ByteString -> W256 -> W256 -> W256 -> ByteString -> ByteString
writeMemory (Int -> Word8 -> ByteString
BS.replicate Int
len' Word8
0) (forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
len') W256
0 (forall a b. (Integral a, Num b) => a -> b
fromIntegral a
at') ByteString
bs
refined :: ByteString
refined = forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr (\(Int
start, Int
len) ByteString
acc -> forall {a}. Integral a => a -> Int -> ByteString -> ByteString
insert Int
start Int
len ByteString
acc) ByteString
raw [(Int, Int)]
holes'
in ByteString -> Int
BS.length ByteString
raw forall a. Eq a => a -> a -> Bool
== ByteString -> Int
BS.length ByteString
template Bool -> Bool -> Bool
&& ByteString
template forall a. Eq a => a -> a -> Bool
== ByteString
refined
showTraceLocation :: DappInfo -> Trace -> Either Text Text
showTraceLocation :: DappInfo -> Trace -> Either Text Text
showTraceLocation DappInfo
dapp Trace
trace =
case DappInfo -> Trace -> Maybe SrcMap
traceSrcMap DappInfo
dapp Trace
trace of
Maybe SrcMap
Nothing -> forall a b. a -> Either a b
Left Text
"<no source map>"
Just SrcMap
sm ->
case SourceCache -> SrcMap -> Maybe (Text, Int)
srcMapCodePos (forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view Lens' DappInfo SourceCache
dappSources DappInfo
dapp) SrcMap
sm of
Maybe (Text, Int)
Nothing -> forall a b. a -> Either a b
Left Text
"<source not found>"
Just (Text
fileName, Int
lineIx) ->
forall a b. b -> Either a b
Right (Text
fileName forall a. Semigroup a => a -> a -> a
<> Text
":" forall a. Semigroup a => a -> a -> a
<> FilePath -> Text
pack (forall a. Show a => a -> FilePath
show Int
lineIx))