packages feed

morley-1.20.0: src/Morley/Michelson/Interpret.hs

-- SPDX-FileCopyrightText: 2021 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

-- | Module, containing function to interpret Michelson
-- instructions against given context and input stack.
module Morley.Michelson.Interpret
  ( ContractEnv
  , ContractEnv' (..)
  , InterpreterState (..)
  , MichelsonFailed (..)
  , MichelsonFailureWithStack(..)
  , RemainingSteps (..)
  , SomeItStack (..)
  , MorleyLogs (..)
  , buildMorleyLogs
  , MorleyLogsBuilder (..)

  , interpret
  , interpretInstr
  , interpretInstrAnnotated
  , InterpretReturn
  , ContractReturn
  , RunEvalOpReturn
  , ResultStateLogs(..)

  , mkInitStack
  , InterpretError (..)
  , InterpretResult
  , ContractResult
  , extractValOps
  , EvalM
  , EvalM'
  , InterpreterStateMonad (..)
  , StkEl (.., StkEl)
  , NoStkElMeta(..)
  , seValueL
  , seMetaL
  , InstrRunner
  , runInstr
  , runInstrNoGas
  , runUnpack

    -- * Views
  , ViewLookupError (..)
  , interpretView
  , getViewByName
  , getViewByNameAndType

    -- * Internals
  , initInterpreterState
  , handleReturn
  , runEvalOp
  , runInstrImpl
  , assignBigMapIds
  , mapToStkEl
  , mapToValue
  , mkStkEl
  , mkDuplicateStkEl
  , runEvalOpT
  , interpret'
  , interpretView'
  , StkElMeta(..)
  , EvalOpT (..)
  , EvalOp

    -- * Prisms
  , _MorleyLogs
  ) where

import Prelude hiding (EQ, GT, LT)

import Control.Lens (makeLensesFor, makePrisms, (<<+=))
import Control.Monad.Except (MonadError, liftEither, throwError)
import Control.Monad.RWS.Strict (RWST, runRWST)
import Control.Monad.Writer (MonadWriter, WriterT, tell)
import Data.Default (Default(..))
import Data.GADT.Compare (GEq(..))
import Data.Map qualified as Map
import Data.Set qualified as Set
import Data.Singletons (demote)
import Data.Singletons.Decide (decideEquality)
import Data.Vinyl (Rec(..), (<+>))
import Data.Vinyl.Recursive (rmap, rtraverse)
import Fmt
  (Buildable(build), blockListF, nameF, pretty, quoteOrIndentF, (++|), (+|), (|+), (|++), (|++^))
import Unsafe qualified (fromIntegral)

import Morley.Michelson.ErrorPos
import Morley.Michelson.Interpret.Pack (packValue')
import Morley.Michelson.Interpret.Unpack (UnpackError, unpackValue')
import Morley.Michelson.Runtime.GState
import Morley.Michelson.TypeCheck (eqType)
import Morley.Michelson.Typed hiding (Branch(..))
import Morley.Michelson.Typed.Instr.Constraints
import Morley.Michelson.Typed.Operation
  (OperationHash(..), OriginationOperation(..), mkContractAddress, mkOriginationOperationHash)
import Morley.Michelson.Untyped (unAnnotation)
import Morley.Tezos.Address
import Morley.Tezos.Address.Alias
import Morley.Tezos.Core (ChainId, Mutez, Timestamp, zeroMutez)
import Morley.Tezos.Crypto
  (KeyHash, OpeningResult(..), blake2b, checkSignature, hashKey, keccak, mkTLTime, openChest,
  sha256, sha3, sha512)
import Morley.Tezos.Crypto.BLS12381 (checkPairing)
import Morley.Util.Lens
import Morley.Util.MismatchError
import Morley.Util.Peano (LongerThan, Peano)
import Morley.Util.PeanoNatural (PeanoNatural(..))
import Morley.Util.Sing (eqParamSing)
import Morley.Util.Typeable

{- $setup
>>> import Morley.Michelson.ErrorPos
>>> import Morley.Michelson.Typed
>>> import Fmt (pretty)
-}

-- | Morley logs appearing as interpreter result.
newtype MorleyLogs = MorleyLogs { unMorleyLogs :: [Text] }
  deriving stock (Show, Eq, Generic)
  deriving newtype (Semigroup, Monoid)
  deriving anyclass (NFData)

instance Buildable MorleyLogs where
  build = blockListF . unMorleyLogs

-- | Morley logs accumulator, for incremental building.
newtype MorleyLogsBuilder = MorleyLogsBuilder (Endo [Text])
  deriving stock (Generic)
  deriving newtype (Default, Semigroup, Monoid)

buildMorleyLogs :: MorleyLogsBuilder -> MorleyLogs
buildMorleyLogs (MorleyLogsBuilder builder) =
  MorleyLogs $ appEndo builder []

instance One MorleyLogsBuilder where
  type OneItem MorleyLogsBuilder = Text
  one log = MorleyLogsBuilder $ Endo (log :)

newtype RemainingSteps = RemainingSteps Word64
  deriving stock (Show, Generic)
  deriving newtype (Eq, Ord, Buildable, Num)

instance NFData RemainingSteps

data InterpreterState = InterpreterState
  { isRemainingSteps :: RemainingSteps
  , isGlobalCounter :: GlobalCounter
  , isBigMapCounter :: BigMapCounter
  } deriving stock (Show, Generic)

instance NFData InterpreterState
makeLensesFor
  [ ("isBigMapCounter", "isBigMapCounterL")
  ]
  ''InterpreterState

makePrisms ''MorleyLogs

-- | Represents a value on the stack. Aside from the value itself, it contains
-- arbitrary user-defined metadata. The metadata in question is defined by
-- 'StkElMeta' type class, the interpreter doesn't know anything about it.
--
-- The metadata should not be copied between different stack elements, the only
-- case where it can be copied is when the stack element is copied in its
-- entirety, i.e. when running instructions like @DUP@.
--
-- To create a new t'StkEl' from a value, 'mkStkEl' should always be used. For
-- duplicated stack elements, 'mkDuplicateStkEl' should be used instead, to
-- still give the user an option to override metadata for the duplicated
-- element.
--
-- The data constructor should almost never be used by itself.
data StkEl meta t = MkStkEl
  { seMeta :: meta t
  , seValue :: Value t
  } deriving stock (Eq, Show)

makeLensesWith postfixLFields ''StkEl

instance (forall t. Eq (meta t)) => GEq (StkEl meta) where
  geq se1 se2
    | Just Refl <- geq (seValue se1) (seValue se2)
    , seMeta se1 == seMeta se2
    = Just Refl
    | otherwise = Nothing

-- | Arbitrary stack element metadata that can be constructed in a particular
-- monad. Interpreter doesn't know anything about metadata, and doesn't try to
-- do anything with it. 'mkStkElMeta' describes how to construct metadata for
-- new stack elements based on its value. When a stack element is duplicated,
-- specifically with instructions like @DUP@, old metadata is passed to
-- 'mkStkElMeta' to provide an opportunity to modify it. Implementation is free
-- to either copy it verbatim or ignore the old and generate new.
--
-- See t'StkEl' documentation for an overview of the motivation and design.
type StkElMeta :: (T -> Type) -> (Type -> Type) -> Constraint
class (forall t. Eq (meta t), forall t. Show (meta t)) => StkElMeta meta m where
  -- | How to construct new metadata.
  mkStkElMeta
    :: Maybe (meta t)
    -- ^ For instructions duplicating the value, this @Just meta@ from the
    -- original instruction, @Nothing@ otherwise
    -> Value t
    -- ^ The value for the new stack element.
    -> m (meta t)

-- | Default metadata that does nothing.
data NoStkElMeta t = NoStkElMeta
  deriving stock (Eq, Show)

instance Applicative m => StkElMeta NoStkElMeta m where
  mkStkElMeta _ _ = pure NoStkElMeta

-- | Make an entirely new t'StkEl' from a value.
mkStkEl :: forall meta t m. (Applicative m, StkElMeta meta m) => Value t -> m (StkEl meta t)
mkStkEl val = flip MkStkEl val <$> mkStkElMeta Nothing val

-- | Make a duplicate t'StkEl', constructing metadata via 'mkStkElMeta'.
mkDuplicateStkEl
  :: forall meta t m. (Applicative m, StkElMeta meta m)
  => StkEl meta t -> m (StkEl meta t)
mkDuplicateStkEl MkStkEl{..} = flip MkStkEl seValue <$> mkStkElMeta (Just seMeta) seValue

pattern StkEl :: Value t -> StkEl meta t
pattern StkEl x <- MkStkEl _ x
{-# COMPLETE StkEl #-}

-- | Helper function to convert a record of @Value@ to @StkEl@.
mapToStkEl
  :: forall meta inp m. (Applicative m, StkElMeta meta m)
  => Rec Value inp -> m (Rec (StkEl meta) inp)
mapToStkEl = rtraverse $ mkStkEl

-- | Helper function to convert a record of @StkEl@ to @Value@.
mapToValue :: Rec (StkEl meta) inp -> Rec Value inp
mapToValue = rmap seValue

type ContractEnv :: Type
type ContractEnv = ContractEnv' EvalOp

-- | Environment for contract execution. Parametrized by the execution monad,
-- i.e. 'EvalOp' by default, but downstream consumers may define their own if
-- using low-level runners.
data ContractEnv' m = ContractEnv
  { ceNow :: Timestamp
  -- ^ Timestamp returned by the 'NOW' instruction.
  , ceMaxSteps :: RemainingSteps
  -- ^ Number of steps after which execution unconditionally terminates.
  , ceBalance :: Mutez
  -- ^ Current amount of mutez of the current contract.
  , ceContracts :: ContractAddress -> m (Maybe ContractState)
  -- ^ Information stored about the existing contracts.
  , ceSelf :: ContractAddress
  -- ^ Address of the interpreted contract.
  , ceSource :: L1Address
  -- ^ The contract that initiated the current transaction. Note that this
  -- contract should in normal operation be an implicit account.
  , ceSender :: L1Address
  -- ^ The contract that initiated the current internal transaction. This may
  -- either be an implicit account or a smart contract.
  , ceAmount :: Mutez
  -- ^ Amount of the current transaction.
  , ceVotingPowers :: VotingPowers
  -- ^ Distribution of voting power.
  , ceChainId :: ChainId
  -- ^ Identifier of the current chain.
  , ceOperationHash :: Maybe OperationHash
  -- ^ Hash of the currently executed operation, required for
  -- correct contract address computation in @CREATE_CONTRACT@ instruction.
  , ceLevel :: Natural
  -- ^ Number of blocks before the given one in the chain
  , ceErrorSrcPos :: ErrorSrcPos
  -- ^ Current source position information
  , ceMinBlockTime :: Natural
  -- ^ Minimum time between blocks
  , ceMetaWrapper :: forall i o. Instr i o -> Instr i o
  -- ^ Saves outer wrapping 'Meta' and 'WithLoc' while traversing the tree,
  -- used internally when reiterating on 'LOOP' and other similar instructions.
  -- If unsure, initialize it with 'id'.
  }

-- | Errors that can be thrown by the interpreter. The @ext@ type variable
-- allow the downstreams consumer to add additional exceptions.
data MichelsonFailed ext where
  MichelsonFailedWith :: (SingI t, ConstantScope t) => Value t -> MichelsonFailed ext
    -- ^ Represents @[FAILED]@ state of a Michelson program. Contains
    -- value that was on top of the stack when @FAILWITH@ was called.
  MichelsonArithError
    :: (Typeable n, Typeable m)
    => ArithError (Value n) (Value m) -> MichelsonFailed ext
  MichelsonGasExhaustion :: MichelsonFailed ext
  MichelsonFailedTestAssert :: Text -> MichelsonFailed ext
  MichelsonUnsupported :: Text -> MichelsonFailed ext
  MichelsonExt :: ext -> MichelsonFailed ext

deriving stock instance Show ext => Show (MichelsonFailed ext)

instance Eq ext => Eq (MichelsonFailed ext) where
  MichelsonFailedWith v1 == MichelsonFailedWith v2 = v1 `eqParamSing` v2
  MichelsonFailedWith _ == _ = False
  MichelsonArithError ae1 == MichelsonArithError ae2 = ae1 `eqParam2` ae2
  MichelsonArithError _ == _ = False
  MichelsonGasExhaustion == MichelsonGasExhaustion = True
  MichelsonGasExhaustion == _ = False
  MichelsonFailedTestAssert t1 == MichelsonFailedTestAssert t2 = t1 == t2
  MichelsonFailedTestAssert _ == _ = False
  MichelsonUnsupported i1 == MichelsonUnsupported i2 = i1 == i2
  MichelsonUnsupported _ == _ = False
  MichelsonExt i1 == MichelsonExt i2 = i1 == i2
  MichelsonExt _ == _ = False

instance Buildable ext => Buildable (MichelsonFailed ext) where
  build =
    \case
      MichelsonFailedWith v ->
        "Reached FAILWITH instruction with" ++| quoteOrIndentF v |++^ ""
      MichelsonArithError v -> build v
      MichelsonGasExhaustion ->
        "Gas limit exceeded on contract execution"
      MichelsonFailedTestAssert t -> build t
      MichelsonUnsupported instr ->
        build instr <> " instruction is not supported."
      MichelsonExt x -> build x

-- | Carries a 'MichelsonFailed' @ext@ error and the 'ErrorSrcPos' at which it was raised
data MichelsonFailureWithStack ext = MichelsonFailureWithStack
  { mfwsFailed :: MichelsonFailed ext
  , mfwsErrorSrcPos :: ErrorSrcPos
  } deriving stock (Show, Generic, Eq)

{- | Pretty-printer for 'MichelsonFailureWithStack'.

>>> let pos = ErrorSrcPos $ SrcPos (Pos 123) (Pos 456)
>>> let largeValue = VList $ replicate 10 VUnit

>>> pretty $ MichelsonFailureWithStack @Void MichelsonGasExhaustion pos
Gas limit exceeded on contract execution at line 124 char 457.

>>> pretty $ MichelsonFailureWithStack @Void (MichelsonFailedWith VUnit) pos
Reached FAILWITH instruction with 'Unit' at line 124 char 457.

>>> pretty $ MichelsonFailureWithStack @Void (MichelsonFailedWith largeValue) pos
Reached FAILWITH instruction with
  { Unit; Unit; Unit; Unit; Unit; Unit; Unit; Unit; Unit; Unit }
at line 124 char 457.

>>> pretty $ MichelsonFailureWithStack @Void (MichelsonFailedWith $ VPair (largeValue, largeValue)) pos
Reached FAILWITH instruction with
  Pair
    { Unit; Unit; Unit; Unit; Unit; Unit; Unit; Unit; Unit; Unit }
    { Unit; Unit; Unit; Unit; Unit; Unit; Unit; Unit; Unit; Unit }
at line 124 char 457.
-}
instance Buildable ext => Buildable (MichelsonFailureWithStack ext) where
  build (MichelsonFailureWithStack err loc)
    | SrcPos (Pos row) (Pos col) <- unErrorSrcPos loc
    = "" ++| err |++ "at line" ++| build (row + 1) |++ "char" ++| build (col + 1) <> "." |++ ""

data InterpretError ext = InterpretError
  { ieLogs :: MorleyLogs
  , ieFailure :: MichelsonFailureWithStack ext
  } deriving stock (Generic)

deriving stock instance Show ext => Show (InterpretError ext)

instance Buildable ext => Buildable (InterpretError ext) where
  build InterpretError{..} = pretty ieFailure

data ResultStateLogs res = ResultStateLogs
  { rslResult :: res
  , rslState :: InterpreterState
  , rslLogs :: MorleyLogs
  } deriving stock (Functor, Foldable, Traversable, Show, Generic)
    deriving anyclass NFData

-- | Pure result of an interpretation, i.e. return value, final interpreter
-- state and execution logs.
type InterpretResult ty = ResultStateLogs (Value ty)

-- | Pure result of contract interpretation. A specialized version of
-- 'InterpretResult'.
type ContractResult ty = InterpretResult (ContractOut1 ty)

-- | Result of 'runEvalOp'. Essentially, return value (possibly failing), state
-- and logs.
type RunEvalOpReturn a = ResultStateLogs (Either (MichelsonFailureWithStack Void) a)

-- | Result of 'interpretView'. A version of 'RunEvalOpReturn' specialized to 'Value'.
type InterpretReturn ty = RunEvalOpReturn (Value ty)

-- | Result of 'interpret'. A version of 'InterpretReturn' specialized to 'ContractOut1'.
type ContractReturn st = InterpretReturn (ContractOut1 st)

-- | On failure, attach logs to failure, but throw away the final state.
handleReturn
  :: InterpretReturn res
  -> Either (InterpretError Void) (ResultStateLogs (Value res))
handleReturn rsl@ResultStateLogs{..} = first (InterpretError rslLogs) . sequence $ rsl

-- | Reset 'ceMetaWrapper` after it is used with an instr.
withMetaWrapper :: forall ext meta m. EvalM' ext m => InstrRunner meta m -> InstrRunner meta m
withMetaWrapper runner instr s = do
  ContractEnv{ceMetaWrapper} <- ask
  local (\env -> env {ceMetaWrapper = id}) $
    runner (ceMetaWrapper instr) s

-- | Extract list of operations from 'ContractOut1' 'Value'.
extractValOps :: Value (ContractOut1 st) -> ([Operation], Value st)
extractValOps (VPair x) = first fromVal x

interpret'
  :: forall cp st arg m. (Monad m)
  => Contract cp st
  -> EntrypointCallT cp arg
  -> Value arg
  -> Value st
  -> ContractEnv' (EvalOpT m)
  -> InterpreterState
  -> m (ContractReturn st)
interpret' Contract{..} epc param initSt env ist = (fmap . fmap) (\(StkEl val :& RNil) -> val) <$>
  runEvalOpT
    (runInstr (unContractCode cCode) =<< mapToStkEl @NoStkElMeta initStack)
    env
    ist
  where
    initStack = mkInitStack (liftCallArg epc param) initSt

mkInitStack
  :: Value param
  -> Value st
  -> Rec Value (ContractInp param st)
mkInitStack param st = VPair (param, st) :& RNil

interpret
  :: Contract cp st
  -> EntrypointCallT cp arg
  -> Value arg
  -> Value st
  -> GlobalCounter
  -> BigMapCounter
  -> ContractEnv
  -> ContractReturn st
interpret contract epc param initSt globalCounter bmCounter env = runIdentity $
  interpret' contract epc param initSt env (initInterpreterState globalCounter bmCounter env)

initInterpreterState :: GlobalCounter -> BigMapCounter -> ContractEnv -> InterpreterState
initInterpreterState globalCounter bmCounter env =
  InterpreterState (ceMaxSteps env) globalCounter bmCounter

-- | Interpret an instruction in vacuum, putting no extra constraints on
-- its execution.
--
-- Mostly for testing purposes.
interpretInstr
  :: ContractEnv
  -> Instr inp out
  -> Rec Value inp
  -> Either (MichelsonFailureWithStack Void) (Rec Value out)
interpretInstr = fmap mapToValue ... interpretInstrAnnotated

-- | Interpret an instruction in vacuum, putting no extra constraints on
-- its execution while preserving its annotations.
--
-- Mostly for testing purposes.
interpretInstrAnnotated
  :: ContractEnv
  -> Instr inp out
  -> Rec Value inp
  -> Either (MichelsonFailureWithStack Void) (Rec (StkEl NoStkElMeta) out)
interpretInstrAnnotated env instr inpSt =
  rslResult $
  runEvalOp
    (runInstr instr =<< mapToStkEl @NoStkElMeta inpSt)
    env
    InterpreterState
      { isRemainingSteps = 9999999999
      , isBigMapCounter = 0
      , isGlobalCounter = 0
      }

data SomeItStack meta where
  SomeItStack :: ExtInstr inp -> Rec (StkEl meta) inp -> SomeItStack meta

-- | The main interpreter monad, used by the higher-level functions like
-- 'interpret' and 'interpretView'.
--
-- Downstream consumers which use 'runInstrImpl' directly may define their own
-- monad similar to this one, or alternatively use 'EvalOpT' with the slightly
-- lower-level functions, e.g. `interpret'` and `interpretView'`.
type EvalOp = EvalOpT Identity

-- | The main interpreter monad transformer. Provides a more convenient way of
-- enriching the interpreter monad without redefining it entirely.
--
-- This is a newtype and not a type synonym due to the reader environment, i.e.
-- t'ContractEnv', being parameterized by the interpreter monad.
newtype EvalOpT m a = EvalOpT
  (ExceptT (MichelsonFailureWithStack Void)
    (RWST (ContractEnv' (EvalOpT m)) MorleyLogsBuilder InterpreterState m) a)
  deriving newtype
    ( MonadError (MichelsonFailureWithStack Void)
    , MonadState InterpreterState
    , InterpreterStateMonad
    , MonadWriter MorleyLogsBuilder
    , MonadReader (ContractEnv' (EvalOpT m))
    , Monad
    , Applicative
    , Functor
    , MonadIO
    )

instance MonadTrans EvalOpT where
  lift = EvalOpT . lift . lift

runEvalOp :: EvalOp a -> ContractEnv -> InterpreterState -> RunEvalOpReturn a
runEvalOp = runIdentity ... runEvalOpT

runEvalOpT
  :: Monad m
  => EvalOpT m a
  -> ContractEnv' (EvalOpT m)
  -> InterpreterState
  -> m (RunEvalOpReturn a)
runEvalOpT (EvalOpT act) env initSt = do
  (rslResult, rslState, buildMorleyLogs -> rslLogs) <- runRWST (runExceptT act) env initSt
  pure ResultStateLogs{..}

class Monad m => InterpreterStateMonad m where
  getInterpreterState :: m InterpreterState
  getInterpreterState = stateInterpreterState (\s -> (s, s))

  putInterpreterState :: InterpreterState -> m ()
  putInterpreterState s = stateInterpreterState (\_ -> ((), s))

  stateInterpreterState :: (InterpreterState -> (a, InterpreterState)) -> m a
  stateInterpreterState f = do
    s <- getInterpreterState
    let (a, s') = f s
    a <$ putInterpreterState s'

  modifyInterpreterState :: (InterpreterState -> InterpreterState) -> m ()
  modifyInterpreterState f = stateInterpreterState (((), ) . f)

instance Monad m => InterpreterStateMonad (StateT InterpreterState m) where
  stateInterpreterState = state
instance (Monad m, Monoid w) => InterpreterStateMonad (RWST r w InterpreterState m) where
  stateInterpreterState = state

instance InterpreterStateMonad m => InterpreterStateMonad (ReaderT r m) where
  stateInterpreterState = lift . stateInterpreterState
instance (InterpreterStateMonad m, Monoid w) => InterpreterStateMonad (WriterT w m) where
  stateInterpreterState = lift . stateInterpreterState
instance {-# OVERLAPPABLE #-} InterpreterStateMonad m => InterpreterStateMonad (StateT w m) where
  stateInterpreterState = lift . stateInterpreterState
instance {-# OVERLAPPABLE #-}
         (InterpreterStateMonad m, Monoid w) => InterpreterStateMonad (RWST r w s m) where
  stateInterpreterState = lift . stateInterpreterState
instance InterpreterStateMonad m => InterpreterStateMonad (ExceptT e m) where
  stateInterpreterState = lift . stateInterpreterState

type EvalM' ext m =
  ( MonadReader (ContractEnv' m) m
  , InterpreterStateMonad m
  , MonadWriter MorleyLogsBuilder m
  , MonadError (MichelsonFailureWithStack ext) m
  )

type EvalM m = EvalM' Void m

type InstrRunner meta m =
  forall inp out.
     Instr inp out
  -> Rec (StkEl meta) inp
  -> m (Rec (StkEl meta) out)

throwMichelson :: EvalM' ext m => MichelsonFailed ext -> m a
throwMichelson mf = asks ceErrorSrcPos >>= throwError . MichelsonFailureWithStack mf

-- | Function to change amount of remaining steps stored in State monad.
runInstr :: forall ext meta m. (StkElMeta meta m, EvalM' ext m) => InstrRunner meta m
runInstr i@(Seq _i1 _i2) r = runInstrImpl runInstr i r
runInstr i@(WithLoc _ _) r = runInstrImpl runInstr i r
runInstr i@(Meta _ _i1) r = runInstrImpl runInstr i r
runInstr i@Nop r = runInstrImpl runInstr i r
runInstr i@(Nested _) r = runInstrImpl runInstr i r
runInstr i@(DocGroup _ _i1) r = runInstrImpl runInstr i r
runInstr i r = do
  rs <- isRemainingSteps <$> getInterpreterState
  if rs == 0
  then throwMichelson MichelsonGasExhaustion
  else do
    modifyInterpreterState (\s -> s {isRemainingSteps = rs - 1})
    runInstrImpl runInstr i r

runInstrNoGas :: EvalM m => InstrRunner NoStkElMeta m
runInstrNoGas = runInstrImpl runInstrNoGas

(<:&>) :: Functor f => f (a r) -> Rec a rs -> f (Rec a (r : rs))
m <:&> r = fmap (:& r) m
infixr 7 <:&>

-- | Function to interpret Michelson instruction(s) against given stack.
-- The @ext@ type variable specifies additional exceptions that can be thrown from the inner
-- runner function (via 'MichelsonExt'). In Morley, it's set to 'Void', but downstream consumers
-- may use other type here.
runInstrImpl
  :: forall ext meta m. (EvalM' ext m, StkElMeta meta m)
  => InstrRunner meta m -> InstrRunner meta m
runInstrImpl runner (Seq i1 i2) r = runner i1 r >>= \r' -> runner i2 r'
runInstrImpl runner (WithLoc ics i) r = do
    -- Add wrapper which will be used later on in loop-like instr.
    let updateEnv env@ContractEnv{..}
          = env { ceErrorSrcPos = ics, ceMetaWrapper = ceMetaWrapper . WithLoc ics }
    local updateEnv $ runner i r
runInstrImpl runner (Meta meta i) r = do
    -- Add wrapper which will be used later on in loop-like instr.
    let updateEnv env@ContractEnv{..}
          = env { ceMetaWrapper = ceMetaWrapper . Meta meta }
    local updateEnv $ runner i r
runInstrImpl _ Nop r = pure $ r
runInstrImpl runner (Ext nop) r = r <$ interpretExt runner (SomeItStack nop r)
runInstrImpl runner (Nested sq) r = runner sq r
runInstrImpl runner (DocGroup _ sq) r = runInstrImpl runner sq r
runInstrImpl _ DROP (_ :& r) = pure $ r
runInstrImpl runner (DROPN n) stack =
  case n of
    Zero    -> pure stack
    Succ s' -> case stack of
      (_ :& r) -> runInstrImpl runner (DROPN s') r
      -- Note: we intentionally do not use `runner` to recursively
      -- interpret `DROPN` here.
      -- All these recursive calls together correspond to a single
      -- Michelson instruction call.
      -- This recursion is implementation detail of `DROPN`.
      -- The same reasoning applies to other instructions parameterized
      -- by a natural number like 'DIPN'.
runInstrImpl _ AnnDUP{} (stkEl :& r) = do
  -- If we're duplicating a big_map, or a value containing big_map(s), we need to generate new big_map ID(s).
  duplicateStkEl <- seValueL assignBigMapIds' =<< mkDuplicateStkEl stkEl
  pure $ duplicateStkEl :& stkEl :& r
runInstrImpl _ (AnnDUPN _ s) stack = go s stack
  where
    go :: forall (n :: Peano) inp out a. ConstraintDUPN n inp out a
       => PeanoNatural n -> Rec (StkEl meta) inp -> m (Rec (StkEl meta) out)
    go (Succ Zero) stk@(stkEl :& _) = do
        -- If we're duplicating a big_map, or a value containing big_map(s), we need to generate new big_map ID(s).
        duplicateStkEl <- seValueL assignBigMapIds' =<< mkDuplicateStkEl stkEl
        pure $ duplicateStkEl :& stk
    go (Succ n@(Succ _)) (b :& r) =
      go n r <&> \case
        (a :& resTail) -> a :& b :& resTail
runInstrImpl _ SWAP (a :& b :& r) = pure $ b :& a :& r
runInstrImpl _ (DIG @_ @_ @_ @a s) input0 =
  pure $ go s input0
  where
    go :: forall (n :: Peano) inp out. ConstraintDIG n inp out a
       => PeanoNatural n -> Rec (StkEl meta) inp -> Rec (StkEl meta) out
    go Zero stack = stack
    go (Succ n') (b :& r) =
      case go n' r of
        a :& resTail -> a :& b :& resTail
runInstrImpl _ (DUG @_ @_ @_ @a s) input0 =
  pure $ go s input0
  where
    go :: forall (n :: Peano) inp out. ConstraintDUG n inp out a
       => PeanoNatural n -> Rec (StkEl meta) inp -> Rec (StkEl meta) out
    go Zero stack = stack
    go (Succ n') (a :& b :& r) = b :& go n' (a :& r)
runInstrImpl _ AnnSOME{} ((seValue -> a) :& r) =
  withValueTypeSanity a $
    mkStkEl (VOption (Just a)) <:&> r
runInstrImpl _ (AnnPUSH _ v) r = mkStkEl v <:&> r
runInstrImpl _ AnnNONE{} r = mkStkEl (VOption Nothing) <:&> r
runInstrImpl _ AnnUNIT{} r = mkStkEl VUnit <:&> r
runInstrImpl runner (IF_NONE _bNone bJust) (StkEl (VOption (Just a)) :& r) =
  runner bJust =<< mkStkEl a <:&> r
runInstrImpl runner (IF_NONE bNone _bJust) (StkEl (VOption Nothing) :& r) =
  runner bNone r
runInstrImpl _ NEVER inp = case inp of {}
runInstrImpl _ (AnnPAIR{}) ((StkEl a) :& (StkEl b) :& r) =
  mkStkEl (VPair (a, b)) <:&> r
runInstrImpl _ (AnnUNPAIR{}) ((StkEl (VPair (a, b))) :& r) = do
  el1 <- mkStkEl a
  el2 <- mkStkEl b
  pure $ el1 :& el2 :& r
runInstrImpl _ (AnnPAIRN _ s) stack = go s stack
  where
    go :: forall n inp. ConstraintPairN n inp
       => PeanoNatural n
       -> Rec (StkEl meta) inp
       -> m (Rec (StkEl meta) (PairN n inp))
    go (Succ (Succ Zero)) (StkEl a :& StkEl b :& r) =
      -- if n=2
      mkStkEl (VPair (a, b)) <:&> r
    go (Succ n@(Succ (Succ _))) (StkEl a :& r@(_ :& _ :& _)) =
      -- if n>2
      go n r >>= \case
        StkEl combed :& r' ->
            mkStkEl (VPair (a, combed)) <:&> r'
runInstrImpl _ (UNPAIRN s) (StkEl pair0 :& r) = do
  r' <- go s pair0
  pure $ r' <+> r
  where
    go
      :: forall n pair. ConstraintUnpairN n pair
      => PeanoNatural n -> Value pair
      -> m (Rec (StkEl meta) (UnpairN n pair))
    go n pair =
      case (n, pair) of
        -- if n=2
        (Succ (Succ Zero), VPair (a, b)) -> do
          el1 <- mkStkEl a
          el2 <- mkStkEl b
          pure $ el1 :& el2 :& RNil
        -- if n>2
        (Succ n'@(Succ (Succ _)), VPair (a, b@(VPair _))) ->
          liftA2 (:&) (mkStkEl a) (go n' b)
runInstrImpl _ AnnCAR{} (StkEl (VPair (a, _b)) :& r) = mkStkEl a <:&> r
runInstrImpl _ AnnCDR{} (StkEl (VPair (_a, b)) :& r) = mkStkEl b <:&> r
runInstrImpl _ AnnLEFT{} ((StkEl a) :& r) =
  withValueTypeSanity a $
    mkStkEl (VOr $ Left a) <:&> r
runInstrImpl _ AnnRIGHT{} ((StkEl b) :& r) =
  withValueTypeSanity b $
    mkStkEl (VOr $ Right b) <:&> r
runInstrImpl runner (IF_LEFT bLeft _) (StkEl (VOr (Left a)) :& r) =
  runner bLeft =<< mkStkEl a <:&> r
runInstrImpl runner (IF_LEFT _ bRight) (StkEl (VOr (Right a)) :& r) =
  runner bRight =<< mkStkEl a <:&> r
runInstrImpl _ AnnNIL{} r = mkStkEl (VList []) <:&> r
runInstrImpl _ AnnCONS{} (a :& StkEl (VList l) :& r) = mkStkEl (VList (seValue a : l)) <:&> r
runInstrImpl runner (IF_CONS _ bNil) (StkEl (VList []) :& r) = runner bNil r
runInstrImpl runner (IF_CONS bCons _) (StkEl (VList (lh : lr)) :& r) = do
  el1 <- mkStkEl lh
  el2 <- mkStkEl (VList lr)
  runner bCons $ el1 :& el2 :& r
runInstrImpl _ AnnSIZE{} (a :& r) =
   mkStkEl (VNat $ Unsafe.fromIntegral @Int @Natural $ evalSize $ seValue a) <:&> r
runInstrImpl _ AnnEMPTY_SET{} r = mkStkEl (VSet Set.empty) <:&> r
runInstrImpl _ AnnEMPTY_MAP{} r = mkStkEl (VMap Map.empty) <:&> r
runInstrImpl _ AnnEMPTY_BIG_MAP{} r = do
  bigMap <- assignBigMapIds' $ VBigMap Nothing Map.empty
  mkStkEl bigMap <:&> r
runInstrImpl runner (AnnMAP _ (code :: Instr (MapOpInp c ': s) (b ': s))) (StkEl a :& r) = do
  -- Evaluation must preserve all stack modifications that @MAP@'s does.
  (newStack, newList) <- foldlM (\(curStack, curList) (val :: StkEl meta (MapOpInp c)) -> do
    res <- runner code (val :& curStack)
    case res of
      ((seValue -> nextVal :: Value b) :& nextStack) -> pure (nextStack, nextVal : curList))
    (r, []) =<< traverse mkStkEl (mapOpToList @c a)
  mkStkEl (mapOpFromList a (reverse newList)) <:&> newStack
runInstrImpl runner (ITER (code :: Instr (IterOpEl c ': s) s)) (StkEl a :& r) =
  case iterOpDetachOne @c a of
    (Just x, xs) -> do
      res <- runner code =<< mkStkEl x <:&> r
      withMetaWrapper runner (ITER code) =<< mkStkEl xs <:&> res
    (Nothing, _) -> pure r
runInstrImpl _ AnnMEM{} (a :& b :& r) = mkStkEl (VBool (evalMem (seValue a) (seValue b))) <:&> r
runInstrImpl _ AnnGET{} (a :& b :& r) = mkStkEl (VOption (evalGet (seValue a) (seValue b))) <:&> r
runInstrImpl _ (AnnGETN _ s) (StkEl pair :& r) = do
  mkStkEl (go s pair) <:&> r
  where
    go
      :: forall ix a. ConstraintGetN ix a
      => PeanoNatural ix -> Value a
      -> Value (GetN ix a)
    go Zero            a                   = a
    go (Succ Zero)      (VPair (left, _))  = left
    go (Succ (Succ n')) (VPair (_, right)) = go n' right
runInstrImpl _ AnnUPDATE{} (a :& b :& StkEl c :& r) =
  mkStkEl (evalUpd (seValue a) (seValue b) c) <:&> r
runInstrImpl _ (AnnUPDATEN _ s) (StkEl (val :: Value val) :& StkEl pair :& r) = do
  mkStkEl (go s pair) <:&> r
  where
    go
      :: forall ix pair. ConstraintUpdateN ix pair
      => PeanoNatural ix -> Value pair -> Value (UpdateN ix val pair)
    go Zero             _                      = val
    go (Succ Zero)      (VPair (_, right))     = VPair (val, right)
    go (Succ (Succ n')) (VPair (left, right))  = VPair (left, go n' right)
runInstrImpl _ AnnGET_AND_UPDATE{} (StkEl key :& StkEl valMb :& StkEl collection :& r) = do
  el1 <- mkStkEl (VOption (evalGet key collection))
  el2 <- mkStkEl (evalUpd key valMb collection)
  pure $ el1 :& el2 :& r
runInstrImpl runner (IF bTrue _) (StkEl (VBool True) :& r) = runner bTrue r
runInstrImpl runner (IF _ bFalse) (StkEl (VBool False) :& r) = runner bFalse r
runInstrImpl _ (LOOP _) (StkEl (VBool False) :& r) = pure r
runInstrImpl runner (LOOP ops) (StkEl (VBool True) :& r) = do
  res <- runner ops r
  withMetaWrapper runner (LOOP ops) res
runInstrImpl _ (LOOP_LEFT _) (StkEl (VOr (Right a)) :& r) = mkStkEl a <:&> r
runInstrImpl runner (LOOP_LEFT ops) (StkEl (VOr (Left a)) :& r) = do
  res <- runner ops =<< mkStkEl a <:&> r
  withMetaWrapper runner (LOOP_LEFT ops) res
runInstrImpl _ (AnnLAMBDA _ lam) r = mkStkEl (mkVLam lam) <:&> r
runInstrImpl _ (AnnLAMBDA_REC _ lam) r = mkStkEl (mkVLamRec lam) <:&> r
runInstrImpl runner AnnEXEC{} (a :& self@(StkEl (VLam code)) :& r) =
  case code of
    LambdaCode (rfAnyInstr -> lBody) -> do
      res <- runner lBody (a :& RNil)
      pure $ res <+> r
    LambdaCodeRec (rfAnyInstr -> lBody) -> do
      res <- runner lBody (a :& self :& RNil)
      pure $ res <+> r
runInstrImpl _ i@AnnAPPLY{} (StkEl (a :: Value a) :& StkEl (VLam code) :& r)
  | _ :: Instr (a : 'TLambda ('TPair a b) c : s) ('TLambda b c : s) <- i
  , _ :: LambdaCode' Instr ('TPair a b) c <- code
  = case code of
      LambdaCode lBody -> mkStkEl (VLam $ LambdaCode (rfMapAnyInstr doApply lBody)) <:&> r
      LambdaCodeRec lBody ->
        let res = RfNormal $ PUSH a `Seq` PAIR `Seq` LAMBDA_REC lBody `Seq` SWAP `Seq` EXEC
        in mkStkEl (VLam $ LambdaCode res) <:&> r
  where
    doApply :: Instr ('TPair a i ': s) o -> Instr (i ': s) o
    doApply b = PUSH a `Seq` PAIR `Seq` Nested b
runInstrImpl runner (DIP i) (a :& r) = do
  res <- runner i r
  pure $ a :& res
runInstrImpl runner (DIPN s i) stack =
  case s of
    Zero -> runner i stack
    Succ s' -> case stack of
      (a :& r) -> (a :&) <$> runInstrImpl runner (DIPN s' i) r
runInstrImpl _ FAILWITH (a :& _) = throwMichelson $ MichelsonFailedWith (seValue a)
runInstrImpl _ AnnCAST{} s = pure s
runInstrImpl _ AnnRENAME{} s = pure s
runInstrImpl _ AnnPACK{} ((seValue -> a) :& r) = mkStkEl (VBytes $ packValue' a) <:&> r
runInstrImpl _ AnnUNPACK{} (StkEl (VBytes a) :& r) =
  mkStkEl (VOption . rightToMaybe $ runUnpack a) <:&> r
runInstrImpl _ AnnCONCAT{} (a :& b :& r) = mkStkEl (evalConcat (seValue a) (seValue b)) <:&> r
runInstrImpl _ AnnCONCAT'{} (StkEl (VList a) :& r) = mkStkEl (evalConcat' a) <:&> r
runInstrImpl _ AnnSLICE{} (StkEl (VNat o) :& StkEl (VNat l) :& StkEl s :& r) =
  mkStkEl (VOption (evalSlice o l s)) <:&> r
runInstrImpl _ AnnISNAT{} (StkEl (VInt i) :& r) =
  if i < 0
  then mkStkEl (VOption Nothing) <:&> r
  else mkStkEl (VOption (Just (VNat $ fromInteger i))) <:&> r
runInstrImpl _ AnnADD{} (l :& r :& rest) = runArithOp (Proxy @Add) l r <:&> rest
runInstrImpl _ AnnSUB{} (l :& r :& rest) = runArithOp (Proxy @Sub) l r <:&> rest
runInstrImpl _ AnnSUB_MUTEZ{} (l :& r :& rest) = runArithOp (Proxy @SubMutez) l r <:&> rest
runInstrImpl _ AnnMUL{} (l :& r :& rest) = runArithOp (Proxy @Mul) l r <:&> rest
runInstrImpl _ AnnEDIV{} (l :& r :& rest) = runArithOp (Proxy @EDiv) l r <:&> rest
runInstrImpl _ AnnABS{} ((seValue -> a) :& rest) =
  mkStkEl (evalUnaryArithOp (Proxy @Abs) a) <:&> rest
runInstrImpl _ AnnNEG{} ((seValue -> a) :& rest) =
  mkStkEl (evalUnaryArithOp (Proxy @Neg) a) <:&> rest
runInstrImpl _ AnnLSL{} (x :& s :& rest) = runArithOp (Proxy @Lsl) x s <:&> rest
runInstrImpl _ AnnLSR{} (x :& s :& rest) = runArithOp (Proxy @Lsr) x s <:&> rest
runInstrImpl _ AnnOR{} (l :& r :& rest) = runArithOp (Proxy @Or) l r <:&> rest
runInstrImpl _ AnnAND{} (l :& r :& rest) = runArithOp (Proxy @And) l r <:&> rest
runInstrImpl _ AnnXOR{} (l :& r :& rest) = runArithOp (Proxy @Xor) l r <:&> rest
runInstrImpl _ AnnNOT{} ((seValue -> a) :& rest) =
  mkStkEl (evalUnaryArithOp (Proxy @Not) a) <:&> rest
runInstrImpl _ AnnCOMPARE{} ((seValue -> l) :& (seValue -> r) :& rest) =
  mkStkEl (VInt (compareOp l r)) <:&> rest
runInstrImpl _ AnnEQ{} ((seValue -> a) :& rest) =
  mkStkEl (evalUnaryArithOp (Proxy @Eq') a) <:&> rest
runInstrImpl _ AnnNEQ{} ((seValue -> a) :& rest) =
  mkStkEl (evalUnaryArithOp (Proxy @Neq) a) <:&> rest
runInstrImpl _ AnnLT{} ((seValue -> a) :& rest) =
  mkStkEl (evalUnaryArithOp (Proxy @Lt) a) <:&> rest
runInstrImpl _ AnnGT{} ((seValue -> a) :& rest) =
  mkStkEl (evalUnaryArithOp (Proxy @Gt) a) <:&> rest
runInstrImpl _ AnnLE{} ((seValue -> a) :& rest) =
  mkStkEl (evalUnaryArithOp (Proxy @Le) a) <:&> rest
runInstrImpl _ AnnGE{} ((seValue -> a) :& rest) =
  mkStkEl (evalUnaryArithOp (Proxy @Ge) a) <:&> rest
runInstrImpl _ AnnINT{} (StkEl a :& r) =
  mkStkEl (evalToIntOp a) <:&> r
runInstrImpl _ AnnNAT{} (StkEl a :& r) =
  mkStkEl (evalToNatOp a) <:&> r
runInstrImpl _ AnnBYTES{} (StkEl a :& r) =
  mkStkEl (evalToBytesOp a) <:&> r
runInstrImpl runner (AnnVIEW (Anns2' _ (_ :: Notes ret)) name)
                    (StkEl (viewArg :: Value arg) :& StkEl (VAddress epAddr) :& r) = do
  res :: Value ('TOption ret) <- VOption <$> runMaybeT do
    EpAddress addr@ContractAddress{} _ <- pure epAddr
    ContractState
      { csContract = viewedContract
      , csStorage = viewedContractStorage
      , csBalance = viewedContractBalance
      } <- MaybeT $ asks ceContracts >>= ($ addr)
    view' <- hoistMaybe $ rightToMaybe $ getViewByNameAndType viewedContract name
    let viewEnv ContractEnv{..} = ContractEnv
          { ceAmount = zeroMutez
          , ceSelf = addr
          , ceSender = Constrained ceSelf
          , ceBalance = viewedContractBalance
          , ..
          }
    lift $ interpretView' runner viewEnv view' viewedContractStorage viewArg
  mkStkEl res <:&> r

runInstrImpl _ (AnnSELF _ sepc :: Instr inp out) r = do
  ContractEnv{..} <- ask
  case Proxy @out of
    (_ :: Proxy ('TContract cp ': s)) -> do
      mkStkEl (VContract (MkAddress ceSelf) sepc) <:&> r
runInstrImpl _ (AnnCONTRACT (Anns2' _ (_ :: Notes a)) instrEpName) (StkEl (VAddress epAddr) :& r) = do
  ContractEnv{..} <- ask
  case epAddr of
    EpAddress' (Constrained addr) addrEpName -> do
      let mepName =
            case (instrEpName, addrEpName) of
              (DefEpName, DefEpName) -> Just DefEpName
              (DefEpName, en) -> Just en
              (en, DefEpName) -> Just en
              _ -> Nothing

      let withNotes v = mkStkEl v <:&> r
      withNotes . VOption =<< case mepName of
        Nothing -> pure Nothing
        Just epName -> case addr of
          ImplicitAddress{} -> pure $ case sing @a of
            STTicket{} -> castContract addr epName $ starParamNotes @a
            STUnit -> castContract addr epName $ starParamNotes @a
            _ -> Nothing
          ContractAddress{} -> runMaybeT do
            ContractState{csContract} <- MaybeT $ ceContracts addr
            hoistMaybe $ castContract addr epName (cParamNotes csContract)
          SmartRollupAddress{} ->
            throwMichelson $ MichelsonUnsupported "sr1 addresses with CONTRACT"
  where
    castContract
      :: forall p kind. (ParameterScope p)
      => KindedAddress kind -> EpName -> ParamNotes p -> Maybe (Value ('TContract a))
    castContract addr epName param = do
      -- As we are within Maybe monad, pattern-match failure results in Nothing
      MkEntrypointCallRes (_ :: Notes a') epc <- mkEntrypointCall epName param
      Right Refl <- pure $ eqType @a @a'
      pure $ VContract (MkAddress addr) (SomeEpc epc)

runInstrImpl _ AnnTRANSFER_TOKENS{}
  (StkEl p :& StkEl (VMutez mutez) :& StkEl contract :& r) = do
    incrementCounter
    globalCounter <- isGlobalCounter <$> getInterpreterState
    mkStkEl (VOp (OpTransferTokens $ TransferTokens p mutez contract globalCounter)) <:&> r
runInstrImpl _ AnnSET_DELEGATE{} (StkEl (VOption mbKeyHash) :& r) = do
  incrementCounter
  globalCounter <- isGlobalCounter <$> getInterpreterState
  case mbKeyHash of
    Just (VKeyHash k) -> mkStkEl (VOp (OpSetDelegate $ SetDelegate (Just k) globalCounter)) <:&> r
    Nothing -> mkStkEl (VOp (OpSetDelegate $ SetDelegate Nothing globalCounter)) <:&> r
runInstrImpl _ (AnnCREATE_CONTRACT _ contract)
  (StkEl (VOption mbKeyHash) :& StkEl (VMutez m) :& StkEl g :& r) = do
  originator <- ceSelf <$> ask
  opHash <- ceOperationHash <$> ask
  incrementCounter
  globalCounter <- isGlobalCounter <$> getInterpreterState
  let resAddr =
        case opHash of
          Just hash -> mkContractAddress hash globalCounter
          Nothing ->
            mkContractAddress
              (mkOriginationOperationHash $
                  createOrigOp originator Nothing mbKeyHash m contract g globalCounter
              )
              -- If opHash is Nothing it means that interpreter is running in some kind of test
              -- context, therefore we generate dummy contract address with its own origination
              -- operation.
              globalCounter
  let resEpAddr = EpAddress resAddr DefEpName
  let resOp = CreateContract
        (Constrained originator) (unwrapMbKeyHash mbKeyHash) m g contract globalCounter
  el1 <- mkStkEl (VOp (OpCreateContract resOp))
  el2 <- mkStkEl (VAddress resEpAddr)
  pure $ el1 :& el2 :& r
runInstrImpl _ AnnIMPLICIT_ACCOUNT{} (StkEl (VKeyHash k) :& r) =
  mkStkEl (VContract (MkAddress $ ImplicitAddress k) sepcPrimitive) <:&> r
runInstrImpl _ AnnNOW{} r = do
  ContractEnv{..} <- ask
  mkStkEl (VTimestamp ceNow) <:&> r
runInstrImpl _ AnnAMOUNT{} r = do
  ContractEnv{..} <- ask
  mkStkEl (VMutez ceAmount) <:&> r
runInstrImpl _ AnnBALANCE{} r = do
  ContractEnv{..} <- ask
  mkStkEl (VMutez ceBalance) <:&> r
runInstrImpl _ AnnVOTING_POWER{} (StkEl (VKeyHash k) :& r) = do
  ContractEnv{..} <- ask
  mkStkEl (VNat $ vpPick k ceVotingPowers) <:&> r
runInstrImpl _ AnnTOTAL_VOTING_POWER{} r = do
  ContractEnv{..} <- ask
  mkStkEl (VNat $ vpTotal ceVotingPowers) <:&> r
runInstrImpl _ AnnCHECK_SIGNATURE{}
  (StkEl (VKey k) :& StkEl (VSignature v) :& StkEl (VBytes b) :& r) =
  mkStkEl (VBool $ checkSignature k v b) <:&> r
runInstrImpl _ AnnSHA256{} (StkEl (VBytes b) :& r) =
  mkStkEl (VBytes $ sha256 b) <:&> r
runInstrImpl _ AnnSHA512{} (StkEl (VBytes b) :& r) =
  mkStkEl (VBytes $ sha512 b) <:&> r
runInstrImpl _ AnnBLAKE2B{} (StkEl (VBytes b) :& r) =
  mkStkEl (VBytes $ blake2b b) <:&> r
runInstrImpl _ AnnSHA3{} (StkEl (VBytes b) :& r) =
  mkStkEl (VBytes $ sha3 b) <:&> r
runInstrImpl _ AnnKECCAK{} (StkEl (VBytes b) :& r) =
  mkStkEl (VBytes $ keccak b) <:&> r
runInstrImpl _ AnnHASH_KEY{} (StkEl (VKey k) :& r) =
  mkStkEl (VKeyHash $ hashKey k) <:&> r
runInstrImpl _ AnnPAIRING_CHECK{} (StkEl (VList pairs) :& r) = do
  let pairs' = [ (g1, g2) | VPair (VBls12381G1 g1, VBls12381G2 g2) <- pairs ]
  mkStkEl (VBool $ checkPairing pairs') <:&> r
runInstrImpl _ AnnSOURCE{} r = do
  ContractEnv{ceSource=Constrained ceSource} <- ask
  mkStkEl (VAddress $ EpAddress ceSource DefEpName) <:&> r
runInstrImpl _ AnnSENDER{} r = do
  ContractEnv{ceSender=Constrained ceSender} <- ask
  mkStkEl (VAddress $ EpAddress ceSender DefEpName) <:&> r
runInstrImpl _ AnnADDRESS{} (StkEl (VContract a sepc) :& r) =
  mkStkEl (VAddress $ EpAddress' a (sepcName sepc)) <:&> r
runInstrImpl _ AnnCHAIN_ID{} r = do
  ContractEnv{..} <- ask
  mkStkEl (VChainId ceChainId) <:&> r
runInstrImpl _ AnnLEVEL{} r = do
  ContractEnv{..} <- ask
  mkStkEl (VNat ceLevel) <:&> r
runInstrImpl _ AnnSELF_ADDRESS{} r = do
  ContractEnv{..} <- ask
  mkStkEl (VAddress $ EpAddress ceSelf DefEpName) <:&> r
runInstrImpl _ AnnTICKET{} (StkEl dat :& StkEl (VNat am) :& r) = do
  ContractEnv{..} <- ask
  let result = VOption do
        guard (am /= 0)
        pure $ VTicket (MkAddress ceSelf) dat am
  mkStkEl result <:&> r
runInstrImpl _ AnnTICKET_DEPRECATED{} (StkEl dat :& StkEl (VNat am) :& r) = do
  ContractEnv{..} <- ask
  mkStkEl (VTicket (MkAddress ceSelf) dat am) <:&> r
runInstrImpl _ AnnREAD_TICKET{} (te@(StkEl (VTicket addr dat am)) :& r) = do
  mkStkEl
      (VPair (VAddress (EpAddress' addr DefEpName), (VPair (dat, VNat am))))
    <:&> te :& r
runInstrImpl _ AnnSPLIT_TICKET{}
    (StkEl tv@(VTicket addr dat am) :&
     StkEl (VPair (VNat am1, VNat am2)) :& r) = do
  let result = withValueTypeSanity tv $ VOption do
        guard (am1 + am2 == am)
        return $ VPair (VTicket addr dat am1, VTicket addr dat am2)
  mkStkEl result <:&> r
runInstrImpl _ AnnJOIN_TICKETS{}
  (StkEl (VPair (tv1@(VTicket addr1 dat1 am1), VTicket addr2 dat2 am2)) :& r) = do
  let result = withValueTypeSanity tv1 $ VOption do
        guard (addr1 == addr2)
        guard (dat1 == dat2)
        return $ VTicket addr1 dat1 (am1 + am2)
  mkStkEl result <:&> r
runInstrImpl _ AnnOPEN_CHEST{}
  (StkEl (VChestKey ck) :& StkEl (VChest c) :& StkEl (VNat nat) :& r) = do
  let result = case mkTLTime nat of
        Right time -> case openChest c ck time of
          Correct bytes -> VOr (Left (VBytes bytes))
          BogusOpening  -> VOr (Right (VBool True))
          BogusCipher   -> VOr (Right (VBool False))
        Left _ -> VOr (Right (VBool True))
  mkStkEl result <:&> r
runInstrImpl _ AnnSAPLING_EMPTY_STATE{} _ = throwMichelson $ MichelsonUnsupported "SAPLING_EMPTY_STATE"
runInstrImpl _ AnnSAPLING_VERIFY_UPDATE{} _ = throwMichelson $ MichelsonUnsupported "SAPLING_VERIFY_UPDATE"
runInstrImpl _ AnnMIN_BLOCK_TIME{} r = do
  ContractEnv{..} <- ask
  mkStkEl (VNat ceMinBlockTime) <:&> r
runInstrImpl _ (AnnEMIT _ (unAnnotation -> emTag) mNotes) ((StkEl emValue) :& r) = do
  incrementCounter
  emCounter <- isGlobalCounter <$> getInterpreterState
  let emNotes = fromMaybe starNotes mNotes
  mkStkEl (VOp (OpEmit Emit{..})) <:&> r

-- | Evaluates an arithmetic operation and either fails or proceeds.
runArithOp
  :: (ArithOp aop n m, StkElMeta meta monad, EvalM' ext monad)
  => proxy aop
  -> StkEl meta n
  -> StkEl meta m
  -> monad (StkEl meta (ArithRes aop n m))
runArithOp op l r = case evalOp op (seValue l) (seValue r) of
  Left  err -> throwMichelson $ MichelsonArithError err
  Right res -> mkStkEl res

-- | Unpacks given raw data into a typed value.
runUnpack
  :: forall t. (UnpackedValScope t)
  => ByteString
  -> Either UnpackError (Value t)
runUnpack bs =
  -- TODO [TM-80]: Gas consumption here should depend on unpacked data size
  -- and size of resulting expression, errors would also spend some (all equally).
  -- Fortunately, the inner decoding logic does not need to know anything about gas use.
  unpackValue' bs

data ViewLookupError
  = ViewNotFound ViewName
  | ViewArgMismatch (MismatchError T)
  | ViewRetMismatch (MismatchError T)
  deriving stock Show

instance Buildable ViewLookupError where
  build = \case
    ViewNotFound name -> "View '" +| name |+ "' not found"
    ViewArgMismatch err -> nameF "View argument type mismatch" err
    ViewRetMismatch err -> nameF "View return type mismatch" err

-- | Interpret a contract's view for given t'ContractEnv' and initial
-- 'InterpreterState'. It is assumed t'ContractEnv' is suitable for the view
-- call, that is, the view is executed exactly in the env that's passed here.
interpretView
  :: View arg st ret
  -> Value st
  -> Value arg
  -> ContractEnv
  -> InterpreterState
  -> InterpretReturn ret
interpretView view' st argument = runEvalOp $
  interpretView' (runInstr @_ @NoStkElMeta) id view' st argument

-- | Attempt to find a view with a given name and given type in a given
-- contract.
getViewByNameAndType
  :: forall arg ret cp st. (SingI arg, SingI ret)
  => Contract cp st -> ViewName -> Either ViewLookupError (View arg st ret)
getViewByNameAndType contract name = do
  SomeView (view'@View{} :: View arg' st ret') <- liftEither $ getViewByName contract name
  let retMismatch = ViewRetMismatch MkMismatchError
        { meActual = demote @ret', meExpected = demote @ret }
      argMismatch = ViewArgMismatch MkMismatchError
        { meActual = demote @arg', meExpected = demote @arg }
  Refl <- liftEither $ maybeToRight argMismatch $ sing @arg `decideEquality` sing @arg'
  Refl <- liftEither $ maybeToRight retMismatch $ sing @ret `decideEquality` sing @ret'
  pure view'

-- | Attempt to find a view with a given name in a given contract.
getViewByName :: Contract cp st -> ViewName -> Either ViewLookupError (SomeView st)
getViewByName contract name =
  liftEither $ maybeToRight (ViewNotFound name) $ lookupView name (cViews contract)

-- | 'EvalM' view interpretation helper.
interpretView'
  :: forall ret st m arg ext meta. (StkElMeta meta m, EvalM' ext m)
  => (forall inp out. Instr inp out -> Rec (StkEl meta) inp -> m (Rec (StkEl meta) out))
  -> (ContractEnv' m -> ContractEnv' m)
  -> View arg st ret
  -> Value st
  -> Value arg
  -> m (Value ret)
interpretView' runner env View{vCode} storage argument = do
  resSt <- local env $ runInstrImpl runner vCode =<<
    mkStkEl @meta (VPair (argument, storage)) <:&> RNil
  let StkEl res :& RNil = resSt
  pure res

createOrigOp
  :: (ParameterScope param, StorageScope store, L1AddressKind kind)
  => KindedAddress kind
  -> Maybe ContractAlias
  -> Maybe (Value 'TKeyHash)
  -> Mutez
  -> Contract param store
  -> Value' Instr store
  -> GlobalCounter
  -> OriginationOperation
createOrigOp originator mbAlias mbDelegate bal contract storage counter =
  OriginationOperation
    { ooOriginator = originator
    , ooDelegate = unwrapMbKeyHash mbDelegate
    , ooBalance = bal
    , ooStorage = storage
    , ooContract = contract
    , ooCounter = counter
    , ooAlias = mbAlias
    }

unwrapMbKeyHash :: Maybe (Value 'TKeyHash) -> Maybe KeyHash
unwrapMbKeyHash mbKeyHash = mbKeyHash <&> \(VKeyHash keyHash) -> keyHash

interpretExt
  :: forall ext meta m. (StkElMeta meta m, EvalM' ext m)
  => InstrRunner meta m -> SomeItStack meta -> m ()
interpretExt _ (SomeItStack (PRINT (PrintComment pc)) st) = do
  let getEl (Left l) = l
      getEl (Right str) = withStackElem str st (pretty . seValue)
  tell . one $ mconcat (map getEl pc)

interpretExt runner (SomeItStack (TEST_ASSERT (TestAssert nm pc instr)) st) = do
  ost <- runInstrImpl runner instr st
  let ((seValue -> fromVal -> succeeded) :& _) = ost
  unless succeeded $ do
    interpretExt runner (SomeItStack (PRINT pc) st)
    throwMichelson $ MichelsonFailedTestAssert $ "TEST_ASSERT " <> nm <> " failed"

interpretExt _ (SomeItStack DOC_ITEM{} _) = pass
interpretExt _ (SomeItStack COMMENT_ITEM{} _) = pass
interpretExt _ (SomeItStack STACKTYPE{} _) = pass

-- | Access given stack reference (in CPS style).
withStackElem
  :: forall st meta a.
     StackRef st
  -> Rec (StkEl meta) st
  -> (forall t. StkEl meta t -> a)
  -> a
withStackElem (StackRef sn) vals cont =
  loop (vals, sn)
  where
    loop
      :: forall s (n :: Peano). (LongerThan s n)
      => (Rec (StkEl meta) s, PeanoNatural n) -> a
    loop = \case
      (e :& _, Zero) -> cont e
      (_ :& es, Succ n) -> loop (es, n)

assignBigMapIds' :: forall ext m t. EvalM' ext m => Value t -> m (Value t)
assignBigMapIds' val = do
  bigMapCounter0 <- view isBigMapCounterL <$> getInterpreterState
  let (storageWithIds, bigMapCounter1) = runState (assignBigMapIds True val) bigMapCounter0
  modifyInterpreterState (set isBigMapCounterL bigMapCounter1)
  pure storageWithIds

-- | All big_maps stored in a chain have a globally unique ID.
--
-- We use this function to assign a new ID whenever a big_map is created.
assignBigMapIds
  :: (MonadState BigMapCounter m)
  => Bool
  -- ^ If true, assign a new ID even if the bigmap already has one.
  -- Otherwise, assign IDs only to bigmaps that don't have one yet.
  -> Value t -> m (Value t)
assignBigMapIds overwriteExistingId =
  dfsTraverseValue def{ dsValueStep = \case
      VBigMap existingId vBigMap | overwriteExistingId || isNothing existingId -> do
        bigMapId <- bigMapCounter <<+= 1
        pure $ VBigMap (Just bigMapId) vBigMap
      v -> pure v
    }

incrementCounter :: (InterpreterStateMonad m) => m ()
incrementCounter = modifyInterpreterState $ \iState ->
      iState { isGlobalCounter = isGlobalCounter iState + 1 }

instance NFData ext => NFData (MichelsonFailed ext) where
  rnf = \case
    MichelsonFailedWith x -> rnf x
    MichelsonArithError x -> rnf x
    MichelsonGasExhaustion -> ()
    MichelsonFailedTestAssert x -> rnf x
    MichelsonUnsupported x -> rnf x
    MichelsonExt x -> rnf x

instance NFData ext => NFData (MichelsonFailureWithStack ext)