{-# Language GADTs #-}
{-# Language NamedFieldPuns #-}
module EVM.Stepper
  ( Action (..)
  , Failure (..)
  , Stepper
  , exec
  , execFully
  , execFullyOrFail
  , decode
  , fail
  , wait
  , evm
  , note
  , entering
  , enter
  )
where
import Prelude hiding (fail)
import Control.Monad.Operational (Program, singleton)
import Data.Binary.Get (runGetOrFail)
import Data.Text (Text)
import EVM (EVM, VMResult (VMFailure, VMSuccess), Error (Query), Query)
import qualified EVM
import EVM.ABI (AbiType, AbiValue, getAbi)
import EVM.Concrete (Blob (B))
import qualified Data.ByteString.Lazy as LazyByteString
data Action a where
  
  Exec ::            Action VMResult
  
  Fail :: Failure -> Action a
  
  Wait :: Query   -> Action ()
  
  EVM  :: EVM a   -> Action a
  
  Note :: Text    -> Action ()
data Failure
  = ContractNotFound
  | DecodingError
  | VMFailed Error
  deriving Show
type Stepper a = Program Action a
exec :: Stepper VMResult
exec = singleton Exec
fail :: Failure -> Stepper a
fail = singleton . Fail
wait :: Query -> Stepper ()
wait = singleton . Wait
evm :: EVM a -> Stepper a
evm = singleton . EVM
note :: Text -> Stepper ()
note = singleton . Note
execFully :: Stepper (Either Error Blob)
execFully =
  exec >>= \case
    VMFailure (Query q) ->
      wait q >> execFully
    VMFailure x ->
      pure (Left x)
    VMSuccess x ->
      pure (Right x)
execFullyOrFail :: Stepper Blob
execFullyOrFail = execFully >>= either (fail . VMFailed) pure
decode :: AbiType -> Blob -> Stepper AbiValue
decode abiType (B bytes) =
  case runGetOrFail (getAbi abiType) (LazyByteString.fromStrict bytes) of
    Right ("", _, x) ->
      pure x
    Right _ ->
      fail DecodingError
    Left _ ->
      fail DecodingError
entering :: Text -> Stepper a -> Stepper a
entering t stepper = do
  evm (EVM.pushTrace (EVM.EntryTrace t))
  x <- stepper
  evm EVM.popTrace
  pure x
enter :: Text -> Stepper ()
enter t = do
  evm (EVM.pushTrace (EVM.EntryTrace t))