-- SPDX-FileCopyrightText: 2021 Oxhead Alpha -- SPDX-License-Identifier: LicenseRef-MIT-OA -- TODO [#712]: Remove this next major release {-# OPTIONS_GHC -Wno-deprecations #-} -- | General-purpose utility functions for typed types. module Morley.Michelson.Typed.Util ( -- * Instruction analysis DfsSettings (..) , CtorEffectsApp (..) , ceaBottomToTop , dfsTraverseInstr , dfsInstr , dfsFoldInstr , dfsModifyInstr , isMichelsonInstr -- * Changing instruction tree structure , linearizeLeft , linearizeLeftDeep -- * Value analysis , dfsFoldMapValue , dfsFoldMapValueM , dfsMapValue , dfsTraverseValue , isStringValue , isBytesValue , allAtomicValues -- * Instruction generation , PushableStorageSplit (..) , splitPushableStorage -- * Working with 'RemFail' , analyzeInstrFailure ) where import Debug qualified (show) import Prelude hiding (Ordering(..)) import Control.Monad.Writer.Strict (Writer, execWriterT, runWriter, tell, writer) import Data.Constraint (Dict(..)) import Data.Default (Default(..)) import Data.Map qualified as M import Data.Set qualified as S import Data.Text.Internal.Builder (fromText) import Fmt (Buildable(..)) import GHC.Exts (fromList) import Morley.Michelson.Text (MText) import Morley.Michelson.Typed.Aliases import Morley.Michelson.Typed.Contract import Morley.Michelson.Typed.Instr import Morley.Michelson.Typed.Scope import Morley.Michelson.Typed.T qualified as T import Morley.Michelson.Typed.Value import Morley.Michelson.Typed.View -- $setup -- >>> :m + Morley.Michelson.Typed.Instr -- | Options for 'dfsTraverseInstr' family of functions. data DfsSettings m = DfsSettings { dsGoToValues :: Bool -- ^ Whether @dfsTraverseInstr@ function should go into values which contain other -- instructions: lambdas and constant contracts -- (which can be passed to @CREATE_CONTRACT@). , dsCtorEffectsApp :: CtorEffectsApp m -- ^ How do we handle intermediate nodes in instruction tree. } -- | Describes how intermediate nodes in instruction tree are accounted. data CtorEffectsApp m = CtorEffectsApp { ceaName :: Text -- ^ Name of this way. , ceaPostStep :: forall i o. Monad m => Instr i o -> m (Instr i o) -> m (Instr i o) -- ^ This transformation is applied after the step. -- It will be provided with the old instruction and the action gathered -- with the recursive traversal for the instruction subtree, -- and the result will go to the parent node. } instance Buildable (CtorEffectsApp x) where build CtorEffectsApp{..} = fromText ceaName -- | Gather effects first for children nodes, then for their parents. {-# DEPRECATED ceaBottomToTop "\"Bottom to top\" is the only available behaviour now." #-} ceaBottomToTop :: CtorEffectsApp x ceaBottomToTop = CtorEffectsApp { ceaName = "Do nothing" , ceaPostStep = const id } instance Default (DfsSettings x) where def = DfsSettings { dsGoToValues = False , dsCtorEffectsApp = ceaBottomToTop } -- | Traverse a typed instruction in depth-first order. -- -- The step action will be applied in bottom-to-top order, i.e. -- first to the children of a node, then to the node itself. dfsTraverseInstr :: forall m inp out. (Monad m) => DfsSettings m -> (forall i o. Instr i o -> m (Instr i o)) -> Instr inp out -> m (Instr inp out) dfsTraverseInstr settings@DfsSettings{..} step i = case i of Seq i1 i2 -> recursion2 Seq i1 i2 WithLoc loc i1 -> recursion1 (WithLoc loc) i1 Meta meta i1 -> recursion1 (Meta meta) i1 InstrWithNotes p notes i1 -> recursion1 (InstrWithNotes p notes) i1 InstrWithVarNotes varNotes i1 -> recursion1 (InstrWithVarNotes varNotes) i1 InstrWithVarAnns varAnns i1 -> recursion1 (InstrWithVarAnns varAnns) i1 FrameInstr p i1 -> recursion1 (FrameInstr p) i1 Nested i1 -> recursion1 Nested i1 DocGroup dg i1 -> recursion1 (DocGroup dg) i1 Fn t sfn i1 -> recursion1 (Fn t sfn) i1 IF_NONE i1 i2 -> recursion2 IF_NONE i1 i2 IF_LEFT i1 i2 -> recursion2 IF_LEFT i1 i2 IF_CONS i1 i2 -> recursion2 IF_CONS i1 i2 IF i1 i2 -> recursion2 IF i1 i2 MAP i1 -> recursion1 MAP i1 ITER i1 -> recursion1 ITER i1 LOOP i1 -> recursion1 LOOP i1 LOOP_LEFT i1 -> recursion1 LOOP_LEFT i1 DIP i1 -> recursion1 DIP i1 DIPN s i1 -> recursion1 (DIPN s) i1 -- This case is more complex so we duplicate @recursion1@ a bit. -- We have to traverse the pushed value because a lambda can be -- somewhere inside of it (e. g. one item of a pair). PUSH v | dsGoToValues -> ceaPostStep dsCtorEffectsApp i do let valueStep :: forall t . Value t -> m (Value t) valueStep = \case -- Using 'analyzeInstrFailure' here (and in case below) is cheap -- (O(n) in total) because we never make it run over the same code twice VLam lambda -> fmap (VLam . analyzeInstrFailure) $ dfsTraverseInstr settings step (rfAnyInstr lambda) otherV -> pure otherV innerV <- dfsTraverseValue valueStep v step $ PUSH innerV | otherwise -> recursion0 i LAMBDA (VLam i1) | dsGoToValues -> recursion1 (LAMBDA . VLam . analyzeInstrFailure) (rfAnyInstr i1) | otherwise -> recursion0 i CREATE_CONTRACT contract | dsGoToValues -> ceaPostStep dsCtorEffectsApp i do codeI <- dfsTraverseInstr settings step (cCode contract) viewsI <- forM (toList $ cViews contract) \(SomeView v) -> do code <- dfsTraverseInstr settings step $ vCode v return $ SomeView v{ vCode = code } step $ CREATE_CONTRACT $ contract { cCode = codeI , cViews = UnsafeViewsSet $ fromList viewsI } | otherwise -> recursion0 i Nop{} -> recursion0 i Ext (TEST_ASSERT (TestAssert nm pc i1)) -> recursion1 (Ext . TEST_ASSERT . TestAssert nm pc) i1 Ext{} -> recursion0 i AnnCAR{} -> recursion0 i AnnCDR{} -> recursion0 i DROP{} -> recursion0 i DROPN{} -> recursion0 i DUP{} -> recursion0 i DUPN{} -> recursion0 i SWAP{} -> recursion0 i DIG{} -> recursion0 i DUG{} -> recursion0 i SOME{} -> recursion0 i NONE{} -> recursion0 i UNIT{} -> recursion0 i AnnPAIR{} -> recursion0 i AnnUNPAIR{} -> recursion0 i PAIRN{} -> recursion0 i UNPAIRN{} -> recursion0 i AnnLEFT{} -> recursion0 i AnnRIGHT{} -> recursion0 i NIL{} -> recursion0 i CONS{} -> recursion0 i SIZE{} -> recursion0 i EMPTY_SET{} -> recursion0 i EMPTY_MAP{} -> recursion0 i EMPTY_BIG_MAP{} -> recursion0 i MEM{} -> recursion0 i GET{} -> recursion0 i GETN{} -> recursion0 i UPDATE{} -> recursion0 i UPDATEN{} -> recursion0 i GET_AND_UPDATE{} -> recursion0 i EXEC{} -> recursion0 i APPLY{} -> recursion0 i FAILWITH{} -> recursion0 i CAST{} -> recursion0 i RENAME{} -> recursion0 i PACK{} -> recursion0 i UNPACK{} -> recursion0 i CONCAT{} -> recursion0 i CONCAT'{} -> recursion0 i SLICE{} -> recursion0 i ISNAT{} -> recursion0 i ADD{} -> recursion0 i SUB{} -> recursion0 i SUB_MUTEZ{} -> recursion0 i MUL{} -> recursion0 i EDIV{} -> recursion0 i ABS{} -> recursion0 i NEG{} -> recursion0 i LSL{} -> recursion0 i LSR{} -> recursion0 i OR{} -> recursion0 i AND{} -> recursion0 i XOR{} -> recursion0 i NOT{} -> recursion0 i COMPARE{} -> recursion0 i EQ{} -> recursion0 i NEQ{} -> recursion0 i LT{} -> recursion0 i GT{} -> recursion0 i LE{} -> recursion0 i GE{} -> recursion0 i INT{} -> recursion0 i VIEW{} -> recursion0 i SELF{} -> recursion0 i CONTRACT{} -> recursion0 i TRANSFER_TOKENS{} -> recursion0 i SET_DELEGATE{} -> recursion0 i IMPLICIT_ACCOUNT{} -> recursion0 i NOW{} -> recursion0 i AMOUNT{} -> recursion0 i BALANCE{} -> recursion0 i VOTING_POWER{} -> recursion0 i TOTAL_VOTING_POWER{} -> recursion0 i CHECK_SIGNATURE{} -> recursion0 i SHA256{} -> recursion0 i SHA512{} -> recursion0 i BLAKE2B{} -> recursion0 i SHA3{} -> recursion0 i KECCAK{} -> recursion0 i HASH_KEY{} -> recursion0 i PAIRING_CHECK{} -> recursion0 i SOURCE{} -> recursion0 i SENDER{} -> recursion0 i ADDRESS{} -> recursion0 i CHAIN_ID{} -> recursion0 i LEVEL{} -> recursion0 i SELF_ADDRESS{} -> recursion0 i NEVER{} -> recursion0 i TICKET{} -> recursion0 i READ_TICKET{} -> recursion0 i SPLIT_TICKET{} -> recursion0 i JOIN_TICKETS{} -> recursion0 i OPEN_CHEST{} -> recursion0 i SAPLING_EMPTY_STATE{} -> recursion0 i SAPLING_VERIFY_UPDATE{} -> recursion0 i where recursion0 :: forall a b. Instr a b -> m (Instr a b) recursion0 i0 = ceaPostStep dsCtorEffectsApp i0 $ step i0 recursion1 :: forall a b c d. (Instr a b -> Instr c d) -> Instr a b -> m (Instr c d) recursion1 constructor i0 = ceaPostStep dsCtorEffectsApp (constructor i0) do innerI <- dfsTraverseInstr settings step i0 step $ constructor innerI recursion2 :: forall i o i1 o1 i2 o2. (Instr i1 o1 -> Instr i2 o2 -> Instr i o) -> Instr i1 o1 -> Instr i2 o2 -> m (Instr i o) recursion2 constructor i1 i2 = ceaPostStep dsCtorEffectsApp (constructor i1 i2) do i1' <- dfsTraverseInstr settings step i1 i2' <- dfsTraverseInstr settings step i2 step $ constructor i1' i2' -- | Traverse a typed instruction in depth-first order. -- '<>' is used to concatenate intermediate results. -- Each instructions can be changed using the supplied @step@ function. -- It does not consider extra instructions (not present in Michelson). {-# DEPRECATED dfsInstr "Use `dfsModifyInstr`, `dfsFoldInstr` or `dfsTraverseInstr` instead." #-} dfsInstr :: forall x inp out. (Monoid x) => DfsSettings (Writer x) -> (forall i o. Instr i o -> (Instr i o, x)) -> Instr inp out -> (Instr inp out, x) dfsInstr settings step i = runWriter $ dfsTraverseInstr settings (writer . step) i -- | Specialization of 'dfsTraverseInstr' for case when changing the instruction is -- not required. dfsFoldInstr :: forall x inp out. (Monoid x) => DfsSettings (Writer x) -> (forall i o. Instr i o -> x) -> Instr inp out -> x dfsFoldInstr settings step instr = snd $ dfsInstr settings (\i -> (i, step i)) instr -- | Specialization of 'dfsTraverseInstr' which only modifies given instruction. dfsModifyInstr :: DfsSettings Identity -> (forall i o. Instr i o -> Instr i o) -> Instr inp out -> Instr inp out dfsModifyInstr settings step instr = runIdentity $ dfsTraverseInstr settings (pure . step) instr -- | Check whether instruction fails at each execution path or have at least one -- non-failing path. -- -- This function assumes that given instruction contains no dead code -- (contract with dead code cannot be valid Michelson contract) and may behave -- in unexpected way if such is present. Term "dead code" includes instructions -- which render into empty Michelson, like Morley extensions. -- On the other hand, this function does not traverse the whole instruction tree; -- performs fastest on left-growing combs. -- -- Often we already have information about instruction failure, use this -- function only in cases when this info is actually unavailable or hard -- to use. analyzeInstrFailure :: HasCallStack => Instr i o -> RemFail Instr i o analyzeInstrFailure = go where go :: Instr i o -> RemFail Instr i o go = \case WithLoc loc i -> case go i of RfNormal i0 -> RfNormal (WithLoc loc i0) r -> r Meta meta i -> case go i of RfNormal i0 -> RfNormal (Meta meta i0) r -> r InstrWithNotes p pn i -> case go i of RfNormal i0 -> RfNormal (InstrWithNotes p pn i0) RfAlwaysFails i0 -> error $ "InstrWithNotes wraps always-failing instruction: " <> Debug.show i0 InstrWithVarNotes vn i -> case go i of RfNormal i0 -> RfNormal (InstrWithVarNotes vn i0) RfAlwaysFails i0 -> error $ "InstrWithVarNotes wraps always-failing instruction: " <> Debug.show i0 InstrWithVarAnns vn i -> case go i of RfNormal i0 -> RfNormal (InstrWithVarAnns vn i0) RfAlwaysFails i0 -> error $ "InstrWithVarAnns wraps always-failing instruction: " <> Debug.show i0 FrameInstr s i -> case go i of RfNormal i0 -> RfNormal (FrameInstr s i0) RfAlwaysFails i0 -> error $ "FrameInstr wraps always-failing instruction: " <> Debug.show i0 Seq a b -> Seq a `rfMapAnyInstr` go b Nop -> RfNormal Nop Ext e -> RfNormal (Ext e) Nested i -> Nested `rfMapAnyInstr` go i DocGroup g i -> DocGroup g `rfMapAnyInstr` go i Fn t sfn i -> Fn t sfn `rfMapAnyInstr` go i IF_NONE l r -> rfMerge IF_NONE (go l) (go r) IF_LEFT l r -> rfMerge IF_LEFT (go l) (go r) IF_CONS l r -> rfMerge IF_CONS (go l) (go r) IF l r -> rfMerge IF (go l) (go r) i@MAP{} -> RfNormal i i@ITER{} -> RfNormal i i@LOOP{} -> RfNormal i i@LOOP_LEFT{} -> RfNormal i i@LAMBDA{} -> RfNormal i i@DIP{} -> RfNormal i i@DIPN{} -> RfNormal i i@AnnCAR{} -> RfNormal i i@AnnCDR{} -> RfNormal i i@DROP{} -> RfNormal i i@DROPN{} -> RfNormal i i@DUP{} -> RfNormal i i@DUPN{} -> RfNormal i i@SWAP{} -> RfNormal i i@DIG{} -> RfNormal i i@DUG{} -> RfNormal i i@PUSH{} -> RfNormal i i@SOME{} -> RfNormal i i@NONE{} -> RfNormal i i@UNIT{} -> RfNormal i i@AnnPAIR{} -> RfNormal i i@AnnUNPAIR{} -> RfNormal i i@PAIRN{} -> RfNormal i i@UNPAIRN{} -> RfNormal i i@AnnLEFT{} -> RfNormal i i@AnnRIGHT{} -> RfNormal i i@NIL{} -> RfNormal i i@CONS{} -> RfNormal i i@SIZE{} -> RfNormal i i@EMPTY_SET{} -> RfNormal i i@EMPTY_MAP{} -> RfNormal i i@EMPTY_BIG_MAP{} -> RfNormal i i@MEM{} -> RfNormal i i@GET{} -> RfNormal i i@GETN{} -> RfNormal i i@UPDATE{} -> RfNormal i i@UPDATEN{} -> RfNormal i i@GET_AND_UPDATE{} -> RfNormal i i@EXEC{} -> RfNormal i i@APPLY{} -> RfNormal i FAILWITH -> RfAlwaysFails FAILWITH i@CAST -> RfNormal i i@RENAME -> RfNormal i i@PACK -> RfNormal i i@UNPACK -> RfNormal i i@CONCAT -> RfNormal i i@CONCAT' -> RfNormal i i@SLICE -> RfNormal i i@ISNAT -> RfNormal i i@ADD -> RfNormal i i@SUB -> RfNormal i i@SUB_MUTEZ -> RfNormal i i@MUL -> RfNormal i i@EDIV -> RfNormal i i@ABS -> RfNormal i i@NEG -> RfNormal i i@LSL -> RfNormal i i@LSR -> RfNormal i i@OR -> RfNormal i i@AND -> RfNormal i i@XOR -> RfNormal i i@NOT -> RfNormal i i@COMPARE -> RfNormal i i@EQ -> RfNormal i i@NEQ -> RfNormal i i@LT -> RfNormal i i@GT -> RfNormal i i@LE -> RfNormal i i@GE -> RfNormal i i@INT -> RfNormal i i@VIEW{} -> RfNormal i i@SELF{} -> RfNormal i i@CONTRACT{} -> RfNormal i i@TRANSFER_TOKENS -> RfNormal i i@SET_DELEGATE -> RfNormal i i@CREATE_CONTRACT{} -> RfNormal i i@IMPLICIT_ACCOUNT -> RfNormal i i@NOW -> RfNormal i i@AMOUNT -> RfNormal i i@BALANCE -> RfNormal i i@VOTING_POWER -> RfNormal i i@TOTAL_VOTING_POWER -> RfNormal i i@CHECK_SIGNATURE -> RfNormal i i@SHA256 -> RfNormal i i@SHA512 -> RfNormal i i@BLAKE2B -> RfNormal i i@SHA3 -> RfNormal i i@KECCAK -> RfNormal i i@HASH_KEY -> RfNormal i i@PAIRING_CHECK -> RfNormal i i@SOURCE -> RfNormal i i@SENDER -> RfNormal i i@ADDRESS -> RfNormal i i@CHAIN_ID -> RfNormal i i@LEVEL -> RfNormal i i@SELF_ADDRESS -> RfNormal i NEVER -> RfAlwaysFails NEVER i@TICKET -> RfNormal i i@READ_TICKET -> RfNormal i i@SPLIT_TICKET -> RfNormal i i@JOIN_TICKETS -> RfNormal i i@OPEN_CHEST -> RfNormal i i@SAPLING_EMPTY_STATE{} -> RfNormal i i@SAPLING_VERIFY_UPDATE{} -> RfNormal i -- | There are many ways to represent a sequence of more than 2 instructions. -- E. g. for @i1; i2; i3@ it can be @Seq i1 $ Seq i2 i3@ or @Seq (Seq i1 i2) i3@. -- This function enforces a particular structure. Specifically, it makes each -- v'Seq' have a single instruction (i. e. not v'Seq') in its second argument. -- This function also erases redundant 'Nop's. -- -- Please note that this function is not recursive, it does not -- linearize contents of @IF@ and similar instructions. linearizeLeft :: Instr inp out -> Instr inp out linearizeLeft = linearizeLeftHelper False where -- In order to avoid quadratic performance we make a simple optimization. -- We track whether left argument of `Seq` is already linearized. -- If it is, we do not need to ever linearize it again. linearizeLeftHelper :: Bool -> Instr inp out -> Instr inp out linearizeLeftHelper isLeftInstrAlreadyLinear = \case Seq i1 (Seq i2 i3) -> linearizeLeftHelper True $ Seq (linearizeLeftHelper isLeftInstrAlreadyLinear (Seq i1 i2)) i3 -- `i2` is not a `Seq`, so we only need to linearize `i1` -- and connect it with `i2`. Seq i1 i2 | isLeftInstrAlreadyLinear , Nop <- i2 -> i1 | isLeftInstrAlreadyLinear -> Seq i1 i2 | Nop <- i2 -> linearizeLeft i1 | otherwise -> Seq (linearizeLeft i1) i2 i -> i -- | \"Deep\" version of 'linearizeLeft'. It recursively linearizes -- instructions stored in other instructions. linearizeLeftDeep :: Instr inp out -> Instr inp out linearizeLeftDeep = dfsModifyInstr def linearizeLeft ---------------------------------------------------------------------------- -- Value analysis ---------------------------------------------------------------------------- -- | Traverse a value in depth-first order. dfsMapValue :: forall t. (forall t'. Value t' -> Value t') -> Value t -> Value t dfsMapValue step v = runIdentity $ dfsTraverseValue (pure . step) v -- | Traverse a value in depth-first order. dfsTraverseValue :: forall t m. (Monad m) => (forall t'. Value t' -> m (Value t')) -> Value t -> m (Value t) dfsTraverseValue step i = case i of -- Atomic VKey{} -> step i VUnit -> step i VSignature{} -> step i VChainId{} -> step i VOp{} -> step i VContract{} -> step i VTicket{} -> step i -- cannot appear as constant in a contract VLam{} -> step i VInt{} -> step i VNat{} -> step i VString{} -> step i VBytes{} -> step i VMutez{} -> step i VBool{} -> step i VKeyHash{} -> step i VBls12381Fr{} -> step i VBls12381G1{} -> step i VBls12381G2{} -> step i VTimestamp{} -> step i VAddress{} -> step i VChestKey{} -> step i VChest{} -> step i -- Non-atomic VOption mVal -> case mVal of Nothing -> step i Just val -> recursion1 (VOption . Just) val VList vals -> do vs <- traverse (dfsTraverseValue step) vals step $ VList vs VSet vals -> do cs <- S.fromList <$> traverse (dfsTraverseValue step) (S.toList vals) step (VSet cs) VPair (v1, v2) -> do v1' <- dfsTraverseValue step v1 v2' <- dfsTraverseValue step v2 step $ VPair (v1', v2') VOr vEither -> case vEither of Left v -> recursion1 (VOr . Left) v Right v -> recursion1 (VOr . Right) v VMap vmap -> mapRecursion VMap vmap VBigMap bmId vmap -> mapRecursion (VBigMap bmId) vmap where recursion1 :: forall t'. (Value t' -> Value t) -> Value t' -> m (Value t) recursion1 constructor v = do v' <- dfsTraverseValue step v step $ constructor v' mapRecursion :: forall k v. Comparable k => (M.Map (Value k) (Value v) -> Value t) -> M.Map (Value k) (Value v) -> m (Value t) mapRecursion constructor vmap = do vmap' <- M.fromList <$> forM (M.toList vmap) \(k, v) -> do k' <- dfsTraverseValue step k v' <- dfsTraverseValue step v pure (k', v') step $ constructor vmap' -- | Specialization of 'dfsMapValue' for case when changing the value is -- not required. dfsFoldMapValue :: Monoid x => (forall t'. Value t' -> x) -> Value t -> x dfsFoldMapValue step v = runIdentity $ dfsFoldMapValueM (pure . step) v -- | Specialization of 'dfsMapValue' for case when changing the value is -- not required. dfsFoldMapValueM :: (Monoid x, Monad m) => (forall t'. Value t' -> m x) -> Value t -> m x dfsFoldMapValueM step v = do execWriterT $ dfsTraverseValue (\val -> do x <- lift $ step val tell x pure val ) v -- | If value is a string, return the stored string. isStringValue :: Value t -> Maybe MText isStringValue = \case VString str -> Just str _ -> Nothing -- | If value is a bytestring, return the stored bytestring. isBytesValue :: Value t -> Maybe ByteString isBytesValue = \case VBytes bytes -> Just bytes _ -> Nothing -- | Takes a selector which checks whether a value can be converted -- to something. Recursively applies it to all values. Collects extracted -- values in a list. allAtomicValues :: forall t a. (forall t'. Value t' -> Maybe a) -> Value t -> [a] allAtomicValues selector = dfsFoldMapValue (maybeToList . selector) -------------------------------------------------------------------------------- -- Instruction generation -------------------------------------------------------------------------------- -- | Result of splitting a storage 'Value' of @st@ on the stack @s@. -- -- The idea behind this is to either: prove that the whole 'Value' can be put on -- the stack without containing a single @big_map@ or to split it into: -- a 'Value' containing its @big_map@s and an instruction to reconstruct the -- storage. -- -- The main idea behind this is to create a large storage in Michelson code to -- then create a contract using @CREATE_CONTRACT@. -- Note: a simpler solution would have been to replace @big_map@ 'Value's with -- an 'EMPTY_BIG_MAP' followed by many 'UPDATE' to push its content, but sadly -- a bug (tezos/tezos/1154) prevents this from being done. data PushableStorageSplit s st where -- | The type of the storage is fully constant. ConstantStorage :: ConstantScope st => Value st -> PushableStorageSplit s st -- | The type of the storage is not a constant, but its value does not contain -- @big_map@s. E.g. A 'Right ()' value of type 'Either (BigMap k v) ()'. PushableValueStorage :: StorageScope st => Instr s (st ': s) -> PushableStorageSplit s st -- | The type of the storage and part of its value (here @heavy@) contain one or -- more @big_map@s or @ticket@s. The instruction can take the non-pushable -- 'Value heavy' and reconstruct the original 'Value st' without using any -- 'EMPTY_BIG_MAP'. PartlyPushableStorage :: (StorageScope heavy, StorageScope st) => Value heavy -> Instr (heavy ': s) (st ': s) -> PushableStorageSplit s st -- | Splits the given storage 'Value' into a 'PushableStorageSplit'. -- -- This is based off the fact that the only storages that cannot be directly -- 'PUSH'ed are the ones that contain 'Morley.Michelson.Typed.Haskell.Value.BigMap's and tickets. -- See difference between 'StorageScope' and 'ConstantScope'. -- -- So what we do here is to create a 'Value' as small as possible with all the -- @big_map@s in it (if any) and an 'Instr' that can use it to rebuild the original -- storage 'Value'. -- -- Note: This is done this way to avoid using 'EMPTY_BIG_MAP' instructions, see -- 'PushableStorageSplit' for motivation. splitPushableStorage :: StorageScope t => Value t -> PushableStorageSplit s t splitPushableStorage v = case v of -- Atomic (except op and contract) VKey{} -> ConstantStorage v VUnit -> ConstantStorage v VSignature{} -> ConstantStorage v VChainId{} -> ConstantStorage v VLam{} -> ConstantStorage v VInt{} -> ConstantStorage v VNat{} -> ConstantStorage v VString{} -> ConstantStorage v VBytes{} -> ConstantStorage v VMutez{} -> ConstantStorage v VBool{} -> ConstantStorage v VKeyHash{} -> ConstantStorage v VBls12381Fr{} -> ConstantStorage v VBls12381G1{} -> ConstantStorage v VBls12381G2{} -> ConstantStorage v VTimestamp{} -> ConstantStorage v VAddress{} -> ConstantStorage v VChest{} -> ConstantStorage v VChestKey{} -> ConstantStorage v VTicket{} -> PartlyPushableStorage v Nop -- Non-atomic VOption (Nothing :: Maybe (Value tm)) -> case checkScope @(ConstantScope tm) of Right Dict -> ConstantStorage $ VOption Nothing Left _ -> PushableValueStorage $ NONE VOption (Just jVal :: Maybe (Value tm)) -> case splitPushableStorage jVal of ConstantStorage _ -> ConstantStorage . VOption $ Just jVal PushableValueStorage instr -> PushableValueStorage $ instr `Seq` SOME PartlyPushableStorage val instr -> PartlyPushableStorage val $ instr `Seq` SOME VList (vals :: [Value tl]) -> case checkScope @(ConstantScope tl) of Right Dict -> ConstantStorage v Left _ -> -- Here we check that even tho the type contains big_maps, we actually -- have big_maps in (one or more of) the values too. let handleList :: Instr s ('T.TList tl ': s) -> Value tl -> Maybe (Instr s ('T.TList tl ': s)) handleList instr ele = case splitPushableStorage ele of ConstantStorage val -> Just $ instr `Seq` PUSH val `Seq` CONS PushableValueStorage eleInstr -> Just $ instr `Seq` eleInstr `Seq` CONS PartlyPushableStorage _ _ -> Nothing in maybe (PartlyPushableStorage v Nop) PushableValueStorage $ foldM handleList NIL vals VSet{} -> ConstantStorage v VPair (v1 :: Value t1, v2 :: Value t2) -> withValueTypeSanity v1 $ withValueTypeSanity v2 $ withDeMorganScope @StorageScope @'T.TPair @t1 @t2 $ let handlePair :: PushableStorageSplit s t2 -> PushableStorageSplit (t2 ': s) t1 -> PushableStorageSplit s ('T.TPair t1 t2) handlePair psp2 psp1 = case (psp2, psp1) of -- at least one side is a constant (ConstantStorage _, ConstantStorage _) -> ConstantStorage v (ConstantStorage val2, _) -> handlePair (PushableValueStorage $ PUSH val2) psp1 (_, ConstantStorage val1) -> handlePair psp2 (PushableValueStorage $ PUSH val1) -- at least one side is a constant or has no big_map values (PushableValueStorage instr2, PushableValueStorage instr1) -> PushableValueStorage $ instr2 `Seq` instr1 `Seq` PAIR (PushableValueStorage instr2, PartlyPushableStorage val1 instr1) -> PartlyPushableStorage val1 $ DIP instr2 `Seq` instr1 `Seq` PAIR (PartlyPushableStorage val2 instr2, PushableValueStorage instr1) -> PartlyPushableStorage val2 $ instr2 `Seq` instr1 `Seq` PAIR -- both sides contain a big_map (PartlyPushableStorage val2 instr2, PartlyPushableStorage val1 instr1) -> PartlyPushableStorage (VPair (val1, val2)) $ UNPAIR `Seq` DIP instr2 `Seq` instr1 `Seq` PAIR in handlePair (splitPushableStorage v2) (splitPushableStorage v1) VOr (Left orVal :: Either (Value t1) (Value t2)) -> withValueTypeSanity orVal $ withDeMorganScope @StorageScope @'T.TOr @t1 @t2 $ case splitPushableStorage orVal of ConstantStorage val -> case checkScope @(ConstantScope t2) of -- note: here we need to check for the opposite branch too Right Dict -> ConstantStorage v Left _ -> PushableValueStorage $ PUSH val `Seq` LEFT PushableValueStorage instr -> PushableValueStorage $ instr `Seq` LEFT PartlyPushableStorage val instr -> PartlyPushableStorage val $ instr `Seq` LEFT VOr (Right orVal :: Either (Value t1) (Value t2)) -> withValueTypeSanity orVal $ withDeMorganScope @StorageScope @'T.TOr @t1 @t2 $ case splitPushableStorage orVal of ConstantStorage val -> case checkScope @(ConstantScope t1) of -- note: here we need to check for the opposite branch too Right Dict -> ConstantStorage v Left _ -> PushableValueStorage $ PUSH val `Seq` RIGHT PushableValueStorage instr -> PushableValueStorage $ instr `Seq` RIGHT PartlyPushableStorage val instr -> PartlyPushableStorage val $ instr `Seq` RIGHT VMap (vMap :: (Map (Value tk) (Value tv))) -> case checkScope @(ConstantScope tk) of Left _ -> -- NOTE: all keys for a map need to be comparable and (even tho it's -- not a superclass) that means they have to be constants. -- I (@pasqu4le) found no exception to this rule, perhaps it should be -- enforced in the type system as well, but it may have more -- downsides than it's worth accepting. error "impossible: all map keys should be PUSHable" Right Dict -> case checkScope @(ConstantScope tv) of Right Dict -> ConstantStorage v _ -> withDeMorganScope @HasNoOp @'T.TMap @tk @tv $ -- Similarly as for lists, here we check that even tho the value type -- contains a big_map, we actually have big_maps in (one or more of) them. let handleMap :: Instr s ('T.TMap tk tv ': s) -> (Value tk, Value tv) -> Maybe (Instr s ('T.TMap tk tv ': s)) handleMap instr (key, ele) = case splitPushableStorage (VOption $ Just ele) of ConstantStorage val -> Just $ instr `Seq` PUSH val `Seq` PUSH key `Seq` UPDATE PushableValueStorage eleInstr -> Just $ instr `Seq` eleInstr `Seq` PUSH key `Seq` UPDATE PartlyPushableStorage _ _ -> Nothing in maybe (PartlyPushableStorage v Nop) PushableValueStorage $ foldM handleMap EMPTY_MAP $ M.toList vMap VBigMap _ _ -> PartlyPushableStorage v Nop -- | Whether this instruction is a real Michelson instruction. -- -- Only the root is in question, children in the instruction tree are not -- accounted for. -- -- >>> isMichelsonInstr (Seq Nop Nop) -- True -- -- >>> isMichelsonInstr (Ext $ COMMENT_ITEM "comment") -- False -- -- This function is helpful e.g. in debugger. isMichelsonInstr :: Instr i o -> Bool isMichelsonInstr = \case WithLoc{} -> False Meta{} -> False InstrWithNotes{} -> False InstrWithVarNotes{} -> False InstrWithVarAnns{} -> False FrameInstr{} -> False Seq{} -> True Nop -> False Ext{} -> False Nested{} -> True DocGroup{} -> False Fn{} -> False AnnCAR{} -> True AnnCDR{} -> True DROP{} -> True DROPN{} -> True DUP{} -> True DUPN{} -> True SWAP{} -> True DIG{} -> True DUG{} -> True PUSH{} -> True SOME{} -> True NONE{} -> True UNIT{} -> True IF_NONE{} -> True AnnPAIR{} -> True AnnUNPAIR{} -> True PAIRN{} -> True UNPAIRN{} -> True AnnLEFT{} -> True AnnRIGHT{} -> True IF_LEFT{} -> True NIL{} -> True CONS{} -> True IF_CONS{} -> True SIZE{} -> True EMPTY_SET{} -> True EMPTY_MAP{} -> True EMPTY_BIG_MAP{} -> True MAP{} -> True ITER{} -> True MEM{} -> True GET{} -> True GETN{} -> True UPDATE{} -> True UPDATEN{} -> True GET_AND_UPDATE{} -> True IF{} -> True LOOP{} -> True LOOP_LEFT{} -> True LAMBDA{} -> True EXEC{} -> True APPLY{} -> True DIP{} -> True DIPN{} -> True FAILWITH{} -> True CAST{} -> True RENAME{} -> True PACK{} -> True UNPACK{} -> True CONCAT{} -> True CONCAT'{} -> True SLICE{} -> True ISNAT{} -> True ADD{} -> True SUB{} -> True SUB_MUTEZ{} -> True MUL{} -> True EDIV{} -> True ABS{} -> True NEG{} -> True LSL{} -> True LSR{} -> True OR{} -> True AND{} -> True XOR{} -> True NOT{} -> True COMPARE{} -> True EQ{} -> True NEQ{} -> True LT{} -> True GT{} -> True LE{} -> True GE{} -> True INT{} -> True VIEW{} -> True SELF{} -> True CONTRACT{} -> True TRANSFER_TOKENS{} -> True SET_DELEGATE{} -> True CREATE_CONTRACT{} -> True IMPLICIT_ACCOUNT{} -> True NOW{} -> True AMOUNT{} -> True BALANCE{} -> True VOTING_POWER{} -> True TOTAL_VOTING_POWER{} -> True CHECK_SIGNATURE{} -> True SHA256{} -> True SHA512{} -> True BLAKE2B{} -> True SHA3{} -> True KECCAK{} -> True HASH_KEY{} -> True PAIRING_CHECK{} -> True SOURCE{} -> True SENDER{} -> True ADDRESS{} -> True CHAIN_ID{} -> True LEVEL{} -> True SELF_ADDRESS{} -> True NEVER{} -> True TICKET{} -> True READ_TICKET{} -> True SPLIT_TICKET{} -> True JOIN_TICKETS{} -> True OPEN_CHEST{} -> True SAPLING_EMPTY_STATE{} -> True SAPLING_VERIFY_UPDATE{} -> True