lorentz-0.6.2: src/Lorentz/Entrypoints/Impl.hs
-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ
-- | Common implementations of entrypoints.
module Lorentz.Entrypoints.Impl
( -- * Ways to implement 'ParameterHasEntrypoints'
EpdPlain
, EpdRecursive
, EpdDelegate
, EpdWithRoot
-- * Implementation details
, PlainEntrypointsC
, EPTree (..)
, BuildEPTree
) where
import qualified Data.Kind as Kind
import Data.Singletons.Prelude (SBool(SFalse, STrue))
import Data.Singletons.Prelude.Eq ((%==))
import Data.Vinyl.Core (Rec(..), (<+>))
import Data.Vinyl.Recursive (rmap)
import Fcf (Eval, Exp)
import qualified Fcf
import qualified GHC.Generics as G
import Util.TypeLits
import Lorentz.Value
import Michelson.Typed
import Michelson.Typed.Haskell.Value (GValueType)
import Michelson.Untyped (FieldAnn, ann, noAnn)
import Util.Fcf (type (<|>), Over2, TyEqSing)
import Util.Type
import Lorentz.Annotation
import Lorentz.Entrypoints.Core
import Lorentz.Entrypoints.Helpers
-- | Implementation of 'ParameterHasEntrypoints' which fits for case when
-- your contract exposes multiple entrypoints via having sum type as its
-- parameter.
--
-- In particular, each constructor would produce a homonymous entrypoint with
-- argument type equal to type of constructor field (each constructor should
-- have only one field).
-- Constructor called 'Default' will designate the default entrypoint.
data EpdPlain
instance PlainEntrypointsC EpdPlain cp => EntrypointsDerivation EpdPlain cp where
type EpdAllEntrypoints EpdPlain cp = PlainAllEntrypointsExt EpdPlain cp
type EpdLookupEntrypoint EpdPlain cp = PlainLookupEntrypointExt EpdPlain cp
epdNotes = (plainEpdNotesExt @EpdPlain @cp, noAnn)
epdCall = plainEpdCallExt @EpdPlain @cp
epdDescs = plainEpdDescsExt @EpdPlain @cp
-- | Extension of 'EpdPlain' on parameters being defined as several nested
-- datatypes.
--
-- In particular, this will traverse sum types recursively, stopping at
-- Michelson primitives (like 'Natural') and constructors with number of
-- fields different from one.
--
-- It does not assign names to intermediate nodes of 'Or' tree, only to the very
-- leaves.
--
-- If some entrypoint arguments have custom 'IsoValue' instance, this
-- derivation way will not work. As a workaround, you can wrap your
-- argument into some primitive (e.g. ':!').
data EpdRecursive
instance PlainEntrypointsC EpdRecursive cp => EntrypointsDerivation EpdRecursive cp where
type EpdAllEntrypoints EpdRecursive cp = PlainAllEntrypointsExt EpdRecursive cp
type EpdLookupEntrypoint EpdRecursive cp = PlainLookupEntrypointExt EpdRecursive cp
epdNotes = (plainEpdNotesExt @EpdRecursive @cp, noAnn)
epdCall = plainEpdCallExt @EpdRecursive @cp
epdDescs = plainEpdDescsExt @EpdRecursive @cp
-- | Extension of 'EpdPlain' on parameters being defined as several nested
-- datatypes.
--
-- In particular, it will traverse the immediate sum type, and require another
-- 'ParameterHasEntrypoints' for the inner complex datatypes. Only those
-- inner types are considered which are the only fields in their respective
-- constructors.
-- Inner types should not themselves declare default entrypoint, we enforce
-- this for better modularity.
-- Each top-level constructor will be treated as entrypoint even if it contains
-- a complex datatype within, in such case that would be an entrypoint
-- corresponding to intermediate node in @or@ tree.
--
-- Comparing to 'EpdRecursive' this gives you more control over where and how
-- entrypoints will be derived.
data EpdDelegate
instance (PlainEntrypointsC EpdDelegate cp) => EntrypointsDerivation EpdDelegate cp where
type EpdAllEntrypoints EpdDelegate cp = PlainAllEntrypointsExt EpdDelegate cp
type EpdLookupEntrypoint EpdDelegate cp = PlainLookupEntrypointExt EpdDelegate cp
epdNotes = (plainEpdNotesExt @EpdDelegate @cp, noAnn)
epdCall = plainEpdCallExt @EpdDelegate @cp
epdDescs = plainEpdDescsExt @EpdDelegate @cp
-- | Extension of 'EpdPlain', 'EpdRecursive', and 'EpdDelegate' which allow specifying root annotation
-- for the parameters.
data EpdWithRoot (r :: Symbol) epd
instance (KnownSymbol r, PlainEntrypointsC deriv cp) => EntrypointsDerivation (EpdWithRoot r deriv) cp where
type EpdAllEntrypoints (EpdWithRoot r deriv) cp =
'(r, cp) ': PlainAllEntrypointsExt deriv cp
type EpdLookupEntrypoint (EpdWithRoot r deriv) cp =
Fcf.Case
'[ Fcf.Is (TyEqSing r) ('Just cp)
, Fcf.Else (PlainLookupEntrypointExt deriv cp)
]
epdNotes = (plainEpdNotesExt @deriv @cp, ann (symbolValT' @r))
epdCall label@(Label :: Label name) = case sing @r %== sing @name of
STrue -> EpConstructed EplArgHere
SFalse -> plainEpdCallExt @deriv @cp label
epdDescs =
(addDescStep @r $
EpCallingDesc
{ epcdArg = Proxy
, epcdEntrypoint = ctorNameToEp @r
, epcdSteps = []
} :& RNil
) <+> plainEpdDescsExt @deriv @cp
type PlainAllEntrypointsExt mode cp = AllEntrypoints mode (BuildEPTree mode cp) cp
type PlainLookupEntrypointExt mode cp = LookupEntrypoint mode (BuildEPTree mode cp) cp
plainEpdNotesExt
:: forall mode cp.
(PlainEntrypointsC mode cp, HasCallStack)
=> Notes (ToT cp)
plainEpdNotesExt = mkEntrypointsNotes @mode @(BuildEPTree mode cp) @cp
plainEpdCallExt
:: forall mode cp name.
(PlainEntrypointsC mode cp, ParameterScope (ToT cp))
=> Label name
-> EpConstructionRes (ToT cp) (Eval (LookupEntrypoint mode (BuildEPTree mode cp) cp name))
plainEpdCallExt = mkEpLiftSequence @mode @(BuildEPTree mode cp) @cp
plainEpdDescsExt
:: forall mode cp.
(PlainEntrypointsC mode cp)
=> Rec EpCallingDesc (PlainAllEntrypointsExt mode cp)
plainEpdDescsExt = mkEpDescs @mode @(BuildEPTree mode cp) @cp
type PlainEntrypointsC mode cp =
( GenericIsoValue cp
, EntrypointsNotes mode (BuildEPTree mode cp) cp
, RequireSumType cp
)
-- | Entrypoints tree - skeleton on 'TOr' tree later used to distinguish
-- between constructors-entrypoints and constructors which consolidate
-- a whole pack of entrypoints.
data EPTree
= EPNode EPTree EPTree
-- ^ We are in the intermediate node and need to go deeper.
| EPLeaf
-- ^ We reached entrypoint argument.
| EPDelegate
-- ^ We reached complex parameter part and will need to ask how to process it.
-- | Build 'EPTree' by parameter type.
type BuildEPTree mode a = GBuildEntrypointsTree mode (G.Rep a)
type family GBuildEntrypointsTree (mode :: Kind.Type) (x :: Kind.Type -> Kind.Type)
:: EPTree where
GBuildEntrypointsTree mode (G.D1 _ x) =
GBuildEntrypointsTree mode x
GBuildEntrypointsTree mode (x G.:+: y) =
'EPNode (GBuildEntrypointsTree mode x) (GBuildEntrypointsTree mode y)
GBuildEntrypointsTree EpdPlain (G.C1 _ _) =
'EPLeaf
GBuildEntrypointsTree EpdRecursive (G.C1 _ x) =
GBuildEntrypointsTree EpdRecursive x
GBuildEntrypointsTree EpdDelegate (G.C1 _ (G.S1 _ (G.Rec0 _))) =
'EPDelegate
GBuildEntrypointsTree EpdDelegate (G.C1 _ _) =
'EPLeaf
GBuildEntrypointsTree mode (G.S1 _ x) =
GBuildEntrypointsTree mode x
GBuildEntrypointsTree _ G.U1 =
'EPLeaf
GBuildEntrypointsTree _ (_ G.:*: _) =
'EPLeaf
GBuildEntrypointsTree mode (G.Rec0 a) =
If (IsPrimitiveValue a)
'EPLeaf
(BuildEPTree mode a)
-- | Traverses sum type and constructs 'Notes' which report
-- constructor names via field annotations.
type EntrypointsNotes mode ep a = (Generic a, GEntrypointsNotes mode ep (G.Rep a))
-- | Makes up notes with proper field annotations for given parameter.
mkEntrypointsNotes
:: forall mode ep a.
(EntrypointsNotes mode ep a, GenericIsoValue a, HasCallStack)
=> Notes (ToT a)
mkEntrypointsNotes = fst $ gMkEntrypointsNotes @mode @ep @(G.Rep a)
-- | Makes up a way to lift entrypoint argument to full parameter.
mkEpLiftSequence
:: forall mode ep a name.
( EntrypointsNotes mode ep a, ParameterScope (ToT a)
, GenericIsoValue a
)
=> Label name
-> EpConstructionRes (ToT a) (Eval (LookupEntrypoint mode ep a name))
mkEpLiftSequence = gMkEpLiftSequence @mode @ep @(G.Rep a)
-- | Makes up descriptions of entrypoints calling.
mkEpDescs
:: forall mode ep a.
(EntrypointsNotes mode ep a)
=> Rec EpCallingDesc (AllEntrypoints mode ep a)
mkEpDescs = gMkDescs @mode @ep @(G.Rep a)
-- | Fetches information about all entrypoints - leaves of 'Or' tree.
type AllEntrypoints mode ep a = GAllEntrypoints mode ep (G.Rep a)
-- | Fetches information about all entrypoints - leaves of 'Or' tree.
type LookupEntrypoint mode ep a = GLookupEntrypoint mode ep (G.Rep a)
-- | Generic traversal for 'EntrypointsNotes'.
class GEntrypointsNotes (mode :: Kind.Type) (ep :: EPTree) (x :: Kind.Type -> Kind.Type) where
type GAllEntrypoints mode ep x :: [(Symbol, Kind.Type)]
type GLookupEntrypoint mode ep x :: Symbol -> Exp (Maybe Kind.Type)
{- | Returns:
1. Notes corresponding to this level;
2. Field annotation for this level (and which should be used one level above).
-}
gMkEntrypointsNotes :: HasCallStack => (Notes (GValueType x), FieldAnn)
gMkEpLiftSequence
:: ParameterScope (GValueType x)
=> Label name
-> EpConstructionRes (GValueType x) (Eval (GLookupEntrypoint mode ep x name))
gMkDescs
:: Rec EpCallingDesc (GAllEntrypoints mode ep x)
instance GEntrypointsNotes mode ep x => GEntrypointsNotes mode ep (G.D1 i x) where
type GAllEntrypoints mode ep (G.D1 i x) = GAllEntrypoints mode ep x
type GLookupEntrypoint mode ep (G.D1 i x) = GLookupEntrypoint mode ep x
gMkEntrypointsNotes = gMkEntrypointsNotes @mode @ep @x
gMkEpLiftSequence = gMkEpLiftSequence @mode @ep @x
gMkDescs = gMkDescs @mode @ep @x
instance (GEntrypointsNotes mode epx x, GEntrypointsNotes mode epy y) =>
GEntrypointsNotes mode ('EPNode epx epy) (x G.:+: y) where
type GAllEntrypoints mode ('EPNode epx epy) (x G.:+: y) =
GAllEntrypoints mode epx x ++ GAllEntrypoints mode epy y
type GLookupEntrypoint mode ('EPNode epx epy) (x G.:+: y) =
Over2 (<|>) (GLookupEntrypoint mode epx x) (GLookupEntrypoint mode epy y)
gMkEntrypointsNotes =
let (xnotes, xann) = gMkEntrypointsNotes @mode @epx @x
(ynotes, yann) = gMkEntrypointsNotes @mode @epy @y
in (NTOr noAnn xann yann xnotes ynotes, noAnn)
gMkEpLiftSequence label =
case sing @(GValueType (x G.:+: y)) of
STOr sl _ -> case (checkOpPresence sl, checkNestedBigMapsPresence sl) of
(OpAbsent, NestedBigMapsAbsent) ->
case gMkEpLiftSequence @mode @epx @x label of
EpConstructed liftSeq -> EpConstructed (EplWrapLeft liftSeq)
EpConstructionFailed ->
case gMkEpLiftSequence @mode @epy @y label of
EpConstructed liftSeq -> EpConstructed (EplWrapRight liftSeq)
EpConstructionFailed -> EpConstructionFailed
gMkDescs =
gMkDescs @mode @epx @x <+> gMkDescs @mode @epy @y
instance ( GHasAnnotation x, KnownSymbol ctor
, ToT (GExtractField x) ~ GValueType x
) =>
GEntrypointsNotes mode 'EPLeaf (G.C1 ('G.MetaCons ctor _1 _2) x) where
type GAllEntrypoints mode 'EPLeaf (G.C1 ('G.MetaCons ctor _1 _2) x) =
'[ '(ctor, GExtractField x) ]
type GLookupEntrypoint mode 'EPLeaf (G.C1 ('G.MetaCons ctor _1 _2) x) =
JustOnEq ctor (GExtractField x)
gMkEntrypointsNotes =
(fst $ gGetAnnotation @x defaultAnnOptions FollowEntrypoint NotGenerateFieldAnn, ctorNameToAnn @ctor)
gMkEpLiftSequence (Label :: Label name) =
case sing @ctor %== sing @name of
STrue -> EpConstructed EplArgHere
SFalse -> EpConstructionFailed
gMkDescs = addDescStep @ctor $
EpCallingDesc
{ epcdArg = Proxy
, epcdEntrypoint = ctorNameToEp @ctor
, epcdSteps = []
} :& RNil
instance (ep ~ 'EPNode epx epy, GEntrypointsNotes mode ep x, KnownSymbol ctor) =>
GEntrypointsNotes mode ('EPNode epx epy) (G.C1 ('G.MetaCons ctor _1 _2) x) where
type GAllEntrypoints mode ('EPNode epx epy) (G.C1 ('G.MetaCons ctor _1 _2) x) =
GAllEntrypoints mode ('EPNode epx epy) x
type GLookupEntrypoint mode ('EPNode epx epy) (G.C1 ('G.MetaCons ctor _1 _2) x) =
GLookupEntrypoint mode ('EPNode epx epy) x
gMkEntrypointsNotes = gMkEntrypointsNotes @mode @ep @x
gMkEpLiftSequence = gMkEpLiftSequence @mode @ep @x
gMkDescs = addDescStep @ctor $ gMkDescs @mode @ep @x
instance ( ep ~ 'EPDelegate, GEntrypointsNotes mode ep x
, KnownSymbol ctor, ToT (GExtractField x) ~ GValueType x
) =>
GEntrypointsNotes mode 'EPDelegate (G.C1 ('G.MetaCons ctor _1 _2) x) where
type GAllEntrypoints mode 'EPDelegate (G.C1 ('G.MetaCons ctor _1 _2) x) =
'(ctor, GExtractField x) ': GAllEntrypoints mode 'EPDelegate x
type GLookupEntrypoint mode 'EPDelegate (G.C1 ('G.MetaCons ctor _1 _2) x) =
Over2 (<|>) (JustOnEq ctor (GExtractField x)) (GLookupEntrypoint mode 'EPDelegate x)
gMkEntrypointsNotes =
let (notes, _rootAnn) = gMkEntrypointsNotes @mode @ep @x
in (notes, ctorNameToAnn @ctor)
gMkEpLiftSequence label@(Label :: Label name) =
case sing @ctor %== sing @name of
STrue -> EpConstructed EplArgHere
SFalse -> gMkEpLiftSequence @mode @ep @x label
gMkDescs = addDescStep @ctor $
EpCallingDesc
{ epcdArg = Proxy
, epcdEntrypoint = ctorNameToEp @ctor
, epcdSteps = []
} :& gMkDescs @mode @ep @x
instance GEntrypointsNotes mode ep x => GEntrypointsNotes mode ep (G.S1 i x) where
type GAllEntrypoints mode ep (G.S1 i x) = GAllEntrypoints mode ep x
type GLookupEntrypoint mode ep (G.S1 i x) = GLookupEntrypoint mode ep x
gMkEntrypointsNotes = gMkEntrypointsNotes @mode @ep @x
gMkEpLiftSequence = gMkEpLiftSequence @mode @ep @x
gMkDescs = gMkDescs @mode @ep @x
instance (EntrypointsNotes EpdRecursive ep a, GenericIsoValue a) =>
GEntrypointsNotes EpdRecursive ep (G.Rec0 a) where
type GAllEntrypoints EpdRecursive ep (G.Rec0 a) = AllEntrypoints EpdRecursive ep a
type GLookupEntrypoint EpdRecursive ep (G.Rec0 a) = LookupEntrypoint EpdRecursive ep a
gMkEntrypointsNotes = (mkEntrypointsNotes @EpdRecursive @ep @a, noAnn)
gMkEpLiftSequence = mkEpLiftSequence @EpdRecursive @ep @a
gMkDescs = mkEpDescs @EpdRecursive @ep @a
instance (ParameterDeclaresEntrypoints a) =>
GEntrypointsNotes EpdDelegate 'EPDelegate (G.Rec0 a) where
type GAllEntrypoints EpdDelegate 'EPDelegate (G.Rec0 a) = AllParameterEntrypoints a
type GLookupEntrypoint EpdDelegate 'EPDelegate (G.Rec0 a) = LookupParameterEntrypoint a
gMkEntrypointsNotes = (fst (pepNotes @a), noAnn)
gMkEpLiftSequence = pepCall @a
gMkDescs = pepDescs @a
instance GEntrypointsNotes mode 'EPLeaf G.U1 where
type GAllEntrypoints mode 'EPLeaf G.U1 = '[]
type GLookupEntrypoint mode 'EPLeaf G.U1 = Fcf.ConstFn 'Nothing
gMkEntrypointsNotes = (starNotes, noAnn)
gMkEpLiftSequence _ = EpConstructionFailed
gMkDescs = RNil
instance Each '[KnownT] [GValueType x, GValueType y] =>
GEntrypointsNotes mode 'EPLeaf (x G.:*: y) where
type GAllEntrypoints mode 'EPLeaf (x G.:*: y) = '[]
type GLookupEntrypoint mode 'EPLeaf (x G.:*: y) = Fcf.ConstFn 'Nothing
gMkEntrypointsNotes = (starNotes, noAnn)
gMkEpLiftSequence _ = EpConstructionFailed
gMkDescs = RNil
-- Return 'Just' iff given entries of type @k1@ are equal.
type family JustOnEq (a :: k1) (b :: k2) :: k1 -> Exp (Maybe k2) where
JustOnEq a b =
Fcf.Case
'[ Fcf.Is (TyEqSing a) ('Just b)
, Fcf.Any 'Nothing
]
-- Get field type under 'G.C1'.
type family GExtractField (x :: Kind.Type -> Kind.Type) where
GExtractField (G.S1 _ x) = GExtractField x
GExtractField (G.Rec0 a) = a
GExtractField G.U1 = ()
addDescStep
:: forall ctor eps.
KnownSymbol ctor
=> Rec EpCallingDesc eps -> Rec EpCallingDesc eps
addDescStep =
let step = EpsWrapIn $ symbolValT' @ctor
in rmap $ \EpCallingDesc{..} ->
EpCallingDesc{ epcdSteps = step : epcdSteps, .. }