{-# 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)] -- for contracts with `immutable` vars.
  , 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
  }

-- | bytecode modulo immutables, to identify contracts
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)
      -- contracts with immutable locations can't be id by hash
    , _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]
      -- Sum up the ABI maps from all the contracts.
    , _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)

-- Dapp unit tests are detected by searching within abi methods
-- that begin with "test" or "prove", that are in a contract with
-- the "IS_TEST()" abi marker, for a given regular expression.
--
-- The regex is matched on the full test method name, including path
-- and contract, i.e. "path/to/file.sol:TestContract.test_name()".
--
-- Tests beginning with "test" are interpreted as concrete tests, whereas
-- tests beginning with "prove" are interpreted as symbolic tests.

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
extractSig :: Test -> Text
extractSig (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))