morley-0.3.0: src/Michelson/Typed/Scope.hs
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
-- | Module, containing restrictions imposed by instruction or value scope.
module Michelson.Typed.Scope
( AllowBigMap
, HasNoBigMap
, HasNoOp
, ForbidBigMap
, ForbidOp
, BigMapPresence (..)
, OpPresence (..)
, BigMapConstraint
, checkOpPresence
, checkBigMapPresence
, checkBigMapConstraint
, opAbsense
, bigMapAbsense
, forbiddenOp
, forbiddenBigMap
, bigMapConstrained
) where
import Data.Constraint (Dict(..))
import Data.Singletons (SingI(..))
import Data.Type.Bool (type (||))
import GHC.TypeLits (ErrorMessage(..), TypeError)
import Michelson.Typed.Sing (Sing(..))
import Michelson.Typed.T (T(..))
-- | Whether this type contains 'TOperation' type.
--
-- In some scopes (constants, parameters, storage) appearing for operation type
-- is prohibited.
-- Operations in input/output of lambdas are allowed without limits though.
type family ContainsOp (t :: T) :: Bool where
ContainsOp ('Tc _) = 'False
ContainsOp 'TKey = 'False
ContainsOp 'TUnit = 'False
ContainsOp 'TSignature = 'False
ContainsOp ('TOption t) = ContainsOp t
ContainsOp ('TList t) = ContainsOp t
ContainsOp ('TSet _) = 'False
ContainsOp 'TOperation = 'True
ContainsOp ('TContract t) = ContainsOp t
ContainsOp ('TPair a b) = ContainsOp a || ContainsOp b
ContainsOp ('TOr a b) = ContainsOp a || ContainsOp b
ContainsOp ('TLambda _ _) = 'False
ContainsOp ('TMap _ v) = ContainsOp v
ContainsOp ('TBigMap _ v) = ContainsOp v
-- | Whether this type contains 'TBigMap' type.
--
-- In some scopes (constants, parameters) appearing for big_map type
-- is prohibited. It is permitted in toplevel left element of storage pair.
-- Big_maps in input/output of lambdas are allowed without limits though.
type family ContainsBigMap (t :: T) :: Bool where
ContainsBigMap ('Tc _) = 'False
ContainsBigMap 'TKey = 'False
ContainsBigMap 'TUnit = 'False
ContainsBigMap 'TSignature = 'False
ContainsBigMap ('TOption t) = ContainsBigMap t
ContainsBigMap ('TList t) = ContainsBigMap t
ContainsBigMap ('TSet _) = 'False
ContainsBigMap 'TOperation = 'False
ContainsBigMap ('TContract t) = ContainsBigMap t
ContainsBigMap ('TPair a b) = ContainsBigMap a || ContainsBigMap b
ContainsBigMap ('TOr a b) = ContainsBigMap a || ContainsBigMap b
ContainsBigMap ('TLambda _ _) = 'False
ContainsBigMap ('TMap _ v) = ContainsBigMap v
ContainsBigMap ('TBigMap _ _) = 'True
-- | Constraint which ensures that operation type does not appear in a given type.
--
-- Not just a type alias in order to be able to partially apply it
-- (e.g. in 'Each').
class (ContainsOp t ~ 'False) => HasNoOp t
instance (ContainsOp t ~ 'False) => HasNoOp t
-- | Constraint which ensures that bigmap does not appear in a given type.
class (ContainsBigMap t ~ 'False) => HasNoBigMap t
instance (ContainsBigMap t ~ 'False) => HasNoBigMap t
-- | Type family to check if t is ill-typed contract storage
-- (it contains bigmap not on the left of its toplevel pair)
--
-- Used in @BigMapConstraint@
type family BadBigMapPair t :: Bool where
BadBigMapPair ('TPair ('TBigMap _ v) b) =
ContainsBigMap v || ContainsBigMap b
BadBigMapPair t = ContainsBigMap t
-- | Constraint which ensures, that @t@ can be used as type of contract storage
-- so it optionally has bigmap only on the left of its toplevel pair
type BigMapConstraint t = BadBigMapPair t ~ 'False
-- | Report a human-readable error about 'TOperation' at a wrong place.
type family FailOnOperationFound (enabled :: Bool) :: Constraint where
FailOnOperationFound 'True =
TypeError ('Text "Operations are not allowed in this scope")
FailOnOperationFound 'False = ()
-- | Report a human-readable error about 'TBigMap' at a wrong place.
type family FailOnBigMapFound (enabled :: Bool) :: Constraint where
FailOnBigMapFound 'True =
TypeError ('Text "BigMaps are not allowed in this scope")
FailOnBigMapFound 'False = ()
-- | This is like 'HasNoOp', it raises a more human-readable error
-- when @t@ type is concrete, but you cannot easily extract a proof
-- of no-operation-presence from it.
--
-- Use it in our eDSL.
type ForbidOp t = FailOnOperationFound (ContainsOp t)
type ForbidBigMap t = FailOnBigMapFound (ContainsBigMap t)
type AllowBigMap t = FailOnBigMapFound (BadBigMapPair t)
-- | Reify 'HasNoOp' contraint from 'ForbidOp'.
forbiddenOp
:: forall t a.
(SingI t, ForbidOp t)
=> (HasNoOp t => a)
-> a
forbiddenOp a =
-- It's not clear now to proof GHC that @HasNoOp t@ is implication of
-- @ForbidOp t@, so we use @error@ below and also disable
-- "-Wredundant-constraints" extension.
case checkOpPresence (sing @t) of
OpAbsent -> a
OpPresent -> error "impossible"
forbiddenBigMap
:: forall t a.
(SingI t, ForbidBigMap t)
=> (HasNoBigMap t => a)
-> a
forbiddenBigMap a =
case checkBigMapPresence (sing @t) of
BigMapAbsent -> a
BigMapPresent -> error "impossible"
checkBigMapConstraint
:: forall t a.
(SingI t, AllowBigMap t)
=> (BigMapConstraint t => a)
-> a
checkBigMapConstraint a =
case bigMapConstrained (sing @t) of
Just Dict -> a
Nothing -> error "impossible"
-- | Whether the type contains 'TOperation', with proof.
data OpPresence (t :: T)
= ContainsOp t ~ 'True => OpPresent
| ContainsOp t ~ 'False => OpAbsent
data BigMapPresence (t :: T)
= ContainsBigMap t ~ 'True => BigMapPresent
| ContainsBigMap t ~ 'False => BigMapAbsent
-- @rvem: IMO, generalization of OpPresence and BigMapPresence to
-- TPresence is not worth it, due to the fact that
-- it will require more boilerplate in checkTPresence implementation
-- than it is already done in checkOpPresence and checkBigMapPresence
-- | Check at runtime whether the given type contains 'TOperation'.
checkOpPresence :: Sing (ty :: T) -> OpPresence ty
checkOpPresence = \case
-- This is a sad amount of boilerplate, but at least
-- there is no chance to make a mistake in it.
-- We can't do in a simpler way while requiring only @Sing ty@ / @SingI ty@,
-- and a more complex constraint would be too unpleasant and confusing to
-- propagate everywhere.
STc _ -> OpAbsent
STKey -> OpAbsent
STSignature -> OpAbsent
STUnit -> OpAbsent
STOption t -> case checkOpPresence t of
OpPresent -> OpPresent
OpAbsent -> OpAbsent
STList t -> case checkOpPresence t of
OpPresent -> OpPresent
OpAbsent -> OpAbsent
STSet _ -> OpAbsent
STOperation -> OpPresent
STContract t -> case checkOpPresence t of
OpPresent -> OpPresent
OpAbsent -> OpAbsent
STPair a b -> case (checkOpPresence a, checkOpPresence b) of
(OpPresent, _) -> OpPresent
(_, OpPresent) -> OpPresent
(OpAbsent, OpAbsent) -> OpAbsent
STOr a b -> case (checkOpPresence a, checkOpPresence b) of
(OpPresent, _) -> OpPresent
(_, OpPresent) -> OpPresent
(OpAbsent, OpAbsent) -> OpAbsent
STLambda _ _ -> OpAbsent
STMap _ v -> case checkOpPresence v of
OpPresent -> OpPresent
OpAbsent -> OpAbsent
STBigMap _ v -> case checkOpPresence v of
OpPresent -> OpPresent
OpAbsent -> OpAbsent
-- | Check at runtime whether the given type contains 'TBigMap'.
checkBigMapPresence :: Sing (ty :: T) -> BigMapPresence ty
checkBigMapPresence = \case
-- More boilerplate to boilerplate god.
STc _ -> BigMapAbsent
STKey -> BigMapAbsent
STSignature -> BigMapAbsent
STUnit -> BigMapAbsent
STOption t -> case checkBigMapPresence t of
BigMapPresent -> BigMapPresent
BigMapAbsent -> BigMapAbsent
STList t -> case checkBigMapPresence t of
BigMapPresent -> BigMapPresent
BigMapAbsent -> BigMapAbsent
STSet _ -> BigMapAbsent
STOperation -> BigMapAbsent
STContract t -> case checkBigMapPresence t of
BigMapPresent -> BigMapPresent
BigMapAbsent -> BigMapAbsent
STPair a b -> case (checkBigMapPresence a, checkBigMapPresence b) of
(BigMapPresent, _) -> BigMapPresent
(_, BigMapPresent) -> BigMapPresent
(BigMapAbsent, BigMapAbsent) -> BigMapAbsent
STOr a b -> case (checkBigMapPresence a, checkBigMapPresence b) of
(BigMapPresent, _) -> BigMapPresent
(_, BigMapPresent) -> BigMapPresent
(BigMapAbsent, BigMapAbsent) -> BigMapAbsent
STLambda _ _ -> BigMapAbsent
STMap _ v -> case checkBigMapPresence v of
BigMapPresent -> BigMapPresent
BigMapAbsent -> BigMapAbsent
STBigMap _ _ ->
BigMapPresent
-- | Check at runtime that the given type does not contain 'TOperation'.
opAbsense :: Sing (t :: T) -> Maybe (Dict $ HasNoOp t)
opAbsense s = case checkOpPresence s of
OpPresent -> Nothing
OpAbsent -> Just Dict
-- | Check at runtime that the given type does not containt 'TBigMap'
bigMapAbsense :: Sing (t :: T) -> Maybe (Dict $ HasNoBigMap t)
bigMapAbsense s = case checkBigMapPresence s of
BigMapPresent -> Nothing
BigMapAbsent -> Just Dict
-- | Check at runtime that the given type optionally has bigmap
-- only on the left of its toplevel pair, which is actuall constraint for
-- bigmap appearance in the storage
bigMapConstrained :: Sing (t :: T) -> Maybe (Dict $ BigMapConstraint t)
bigMapConstrained = \case
-- Yet another bunch of boilerplate
-- We have to make pattern matching on first argument of STPair
-- to prove, that BigMap appears only on its leftmost position,
-- and also make pattern matching on all Sing constructors
-- to prove that TPair appears only in STPair
STPair (STBigMap _ v) b ->
case (bigMapAbsense v, bigMapAbsense b) of
(Just Dict, Just Dict) -> Just Dict
_ -> Nothing
STPair (STc _) b -> case bigMapAbsense b of
Just Dict -> Just Dict
Nothing -> Nothing
STPair STKey b -> case bigMapAbsense b of
Just Dict -> Just Dict
Nothing -> Nothing
STPair STUnit b -> case bigMapAbsense b of
Just Dict -> Just Dict
Nothing -> Nothing
STPair STSignature b -> case bigMapAbsense b of
Just Dict -> Just Dict
Nothing -> Nothing
STPair (STOption a) b -> case (bigMapAbsense a, bigMapAbsense b) of
(Just Dict, Just Dict) -> Just Dict
_ -> Nothing
STPair (STList a) b -> case (bigMapAbsense a, bigMapAbsense b) of
(Just Dict, Just Dict) -> Just Dict
_ -> Nothing
STPair (STSet _) b -> case bigMapAbsense b of
Just Dict -> Just Dict
Nothing -> Nothing
STPair STOperation b -> case bigMapAbsense b of
Just Dict -> Just Dict
Nothing -> Nothing
STPair (STContract a) b -> case (bigMapAbsense a, bigMapAbsense b) of
(Just Dict, Just Dict) -> Just Dict
_ -> Nothing
STPair (STPair a b) c ->
case (bigMapAbsense a, bigMapAbsense b, bigMapAbsense c) of
(Just Dict, Just Dict, Just Dict) -> Just Dict
_ -> Nothing
STPair (STOr a b) c ->
case (bigMapAbsense a, bigMapAbsense b, bigMapAbsense c) of
(Just Dict, Just Dict, Just Dict) -> Just Dict
_ -> Nothing
STPair (STLambda _ _) c ->
case bigMapAbsense c of
Just Dict -> Just Dict
Nothing -> Nothing
STPair (STMap _ v) b -> case (bigMapAbsense v, bigMapAbsense b) of
(Just Dict, Just Dict) -> Just Dict
_ -> Nothing
STc _ -> Just Dict
STKey -> Just Dict
STSignature -> Just Dict
STUnit -> Just Dict
STOption t -> case bigMapAbsense t of
Just Dict -> Just Dict
Nothing -> Nothing
STList t -> case bigMapAbsense t of
Just Dict -> Just Dict
Nothing -> Nothing
STSet _ -> Just Dict
STOperation -> Just Dict
STContract t -> case bigMapAbsense t of
Just Dict -> Just Dict
Nothing -> Nothing
STOr a b -> case (bigMapAbsense a, bigMapAbsense b) of
(Just Dict, Just Dict) -> Just Dict
_ -> Nothing
STLambda _ _ -> Just Dict
STMap _ v -> case bigMapAbsense v of
Just Dict -> Just Dict
Nothing -> Nothing
STBigMap _ _ -> Nothing