{-# OPTIONS_GHC -Wno-redundant-constraints #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Lorentz.UParam
( UParam (..)
, EntryPointKind
, type (?:)
, mkUParam
, ConstrainedSome (..)
, UnpackUParam
, unpackUParam
, SomeInterface
, UParam_
, EntryPointsImpl
, UParamFallback
, EntryPointLookupError (..)
, CaseUParam
, caseUParam
, caseUParamT
, uparamFallbackFail
, LookupEntryPoint
, RequireUniqueEntryPoints
, uparamFromAdt
, UParamLinearize
, UParamLinearized
, pbsUParam
, unwrapUParam
) where
import Data.Constraint ((\\))
import qualified Data.Kind as Kind
import Data.Vinyl.Core (Rec(..))
import Data.Vinyl.Derived (Label)
import Data.Vinyl.TypeLevel (type (++))
import qualified Fcf
import Fmt (Buildable(..))
import GHC.Generics ((:*:)(..), (:+:)(..))
import qualified GHC.Generics as G
import GHC.TypeLits (KnownSymbol, Symbol)
import qualified Text.Show
import Lorentz.ADT
import Lorentz.Base
import Lorentz.Coercions
import Lorentz.EntryPoints.Doc
import Lorentz.Constraints
import Lorentz.Errors
import Lorentz.Instr as L
import Lorentz.Macro
import Lorentz.Pack
import Michelson.Text
import Michelson.Typed
import Util.Type
import Util.TypeLits
import Util.TypeTuple
import Util.Markdown
type EntryPointKind = (Symbol, Kind.Type)
type (n :: Symbol) ?: (a :: k) = '(n, a)
newtype UParam (entries :: [EntryPointKind]) = UParamUnsafe (MText, ByteString)
deriving stock (Generic, Eq, Show)
deriving anyclass (IsoValue)
instance Wrapped (UParam entries)
type SomeInterface = '[ '("SomeEntrypoints", Void) ]
type UParam_ = UParam SomeInterface
instance SameEntries entries1 entries2 =>
UParam entries1 `CanCastTo` UParam entries2
type family SameEntries (es1 :: [EntryPointKind]) (es :: [EntryPointKind])
:: Constraint where
SameEntries e e = ()
SameEntries SomeInterface _ = ()
SameEntries _ SomeInterface = ()
SameEntries e1 e2 = (e1 ~ e2)
type family LookupEntryPoint (name :: Symbol) (entries :: [EntryPointKind])
:: Kind.Type where
LookupEntryPoint name ('(name, a) ': _) = a
LookupEntryPoint name (_ ': entries) = LookupEntryPoint name entries
LookupEntryPoint name '[] =
TypeError ('Text "Entry point " ':<>: 'ShowType name ':<>:
'Text " in not in the entry points list")
type family RequireUniqueEntryPoints (entries :: [EntryPointKind])
:: Constraint where
RequireUniqueEntryPoints entries =
RequireAllUnique "entrypoint" (Fcf.Eval (Fcf.Map Fcf.Fst entries))
mkUParam
:: ( KnownSymbol name, NicePackedValue a
, LookupEntryPoint name entries ~ a
, RequireUniqueEntryPoints entries
)
=> Label name -> a -> UParam entries
mkUParam label (a :: a) =
UParamUnsafe (labelToMText label, lPackValue a)
\\ nicePackedValueEvi @a
type MyEntryPoints =
[ "add" ?: Integer
, "reset" ?: ()
]
_mkParamSample1 :: UParam MyEntryPoints
_mkParamSample1 = mkUParam #add 5
data ConstrainedSome (c :: Kind.Type -> Constraint) where
ConstrainedSome :: c a => a -> ConstrainedSome c
instance Show (ConstrainedSome Show) where
show (ConstrainedSome a) = show a
instance Buildable (ConstrainedSome Buildable) where
build (ConstrainedSome a) = build a
class UnpackUParam (c :: Kind.Type -> Constraint) entries where
unpackUParam ::
UParam entries -> Either EntryPointLookupError (MText, ConstrainedSome c)
instance UnpackUParam c '[] where
unpackUParam (UParamUnsafe (name, _)) = Left (NoSuchEntryPoint name)
instance
( KnownSymbol name
, UnpackUParam c entries
, NiceUnpackedValue arg
, c arg
) => UnpackUParam c ((name ?: arg) ': entries) where
unpackUParam (UParamUnsafe (name, bytes))
| name == symbolToMText @name =
fmap (name,) .
first (const ArgumentUnpackFailed) .
fmap ConstrainedSome .
lUnpackValue @arg $
bytes
| otherwise = unpackUParam @c @entries (UParamUnsafe (name, bytes))
unwrapUParam :: UParam entries : s :-> (MText, ByteString) : s
unwrapUParam = coerceUnwrap
data CaseClauseU inp out (entry :: EntryPointKind) where
CaseClauseU :: (arg : inp) :-> out -> CaseClauseU inp out '(name, arg)
instance (name ~ name', body ~ ((arg : inp) :-> out)) =>
CaseArrow name' body (CaseClauseU inp out '(name, arg)) where
(/->) _ = CaseClauseU
data EntryPointLookupError
= NoSuchEntryPoint MText
| ArgumentUnpackFailed
deriving stock (Generic, Eq, Show)
instance Buildable EntryPointLookupError where
build =
\case
NoSuchEntryPoint name -> "No such entrypoint: " <> build name
ArgumentUnpackFailed -> "UNPACK failed"
type instance ErrorArg "uparamNoSuchEntryPoint" = MText
type instance ErrorArg "uparamArgumentUnpackFailed" = ()
instance Buildable (CustomError "uparamNoSuchEntryPoint") where
build (CustomError _ name) = "No such entrypoint: " <> build name
instance Buildable (CustomError "uparamArgumentUnpackFailed") where
build (CustomError _ ()) = "UNPACK failed"
instance CustomErrorHasDoc "uparamNoSuchEntryPoint" where
customErrClass = ErrClassBadArgument
customErrDocMdCause = "Entrypoint with given name does not exist."
instance CustomErrorHasDoc "uparamArgumentUnpackFailed" where
customErrClass = ErrClassBadArgument
customErrDocMdCause = "Argument of wrong type is provided to the entrypoint."
type EntryPointsImpl inp out entries =
Rec (CaseClauseU inp out) entries
type UParamFallback inp out = ((MText, ByteString) : inp) :-> out
uparamFallbackFail :: UParamFallback inp out
uparamFallbackFail =
car # failCustom #uparamNoSuchEntryPoint
class CaseUParam (entries :: [EntryPointKind]) where
caseUParamUnsafe
:: Rec (CaseClauseU inp out) entries
-> UParamFallback inp out
-> (UParam entries : inp) :-> out
instance CaseUParam '[] where
caseUParamUnsafe RNil fallback = unwrapUParam # fallback
instance ( KnownSymbol name
, CaseUParam entries
, NiceUnpackedValue arg
) =>
CaseUParam ((name ?: arg) ': entries) where
caseUParamUnsafe (CaseClauseU clause :& clauses) fallback =
dup # unwrapUParam # car #
push (mkMTextUnsafe $ symbolValT' @name) # eq #
if_ (unwrapUParam # cdr # unpack #
ifSome nop (failCustom_ #uparamArgumentUnpackFailed) # clause)
(cutUParamEntry # caseUParamUnsafe clauses fallback)
where
cutUParamEntry :: UParam (e : es) : s :-> UParam es : s
cutUParamEntry = forcedCoerce_
caseUParam
:: (CaseUParam entries, RequireUniqueEntryPoints entries)
=> Rec (CaseClauseU inp out) entries
-> UParamFallback inp out
-> (UParam entries : inp) :-> out
caseUParam = caseUParamUnsafe
caseUParamT
:: forall entries inp out clauses.
( clauses ~ Rec (CaseClauseU inp out) entries
, RecFromTuple clauses
, CaseUParam entries
)
=> IsoRecTuple clauses
-> UParamFallback inp out
-> (UParam entries : inp) :-> out
caseUParamT clauses fallback = caseUParamUnsafe (recFromTuple clauses) fallback
_caseSample :: UParam MyEntryPoints : s :-> Integer : s
_caseSample = caseUParamT
( #add /-> nop
, #reset /-> L.drop @() # push 0
) uparamFallbackFail
uparamFromAdt
:: UParamLinearize up
=> up -> UParam (UParamLinearized up)
uparamFromAdt = adtToRec . G.from
type UParamLinearize p = (Generic p, GUParamLinearize (G.Rep p))
type UParamLinearized p = GUParamLinearized (G.Rep p)
class GUParamLinearize (x :: Kind.Type -> Kind.Type) where
type GUParamLinearized x :: [(Symbol, Kind.Type)]
adtToRec :: x p -> UParam (GUParamLinearized x)
instance GUParamLinearize x => GUParamLinearize (G.D1 i x) where
type GUParamLinearized (G.D1 i x) = GUParamLinearized x
adtToRec = adtToRec . G.unM1
instance (GUParamLinearize x, GUParamLinearize y) => GUParamLinearize (x :+: y) where
type GUParamLinearized (x :+: y) = GUParamLinearized x ++ GUParamLinearized y
adtToRec = \case
G.L1 x -> let UParamUnsafe up = adtToRec x in UParamUnsafe up
G.R1 y -> let UParamUnsafe up = adtToRec y in UParamUnsafe up
instance (KnownSymbol name, NicePackedValue a) =>
GUParamLinearize (G.C1 ('G.MetaCons name _1 _2) (G.S1 si (G.Rec0 a))) where
type GUParamLinearized (G.C1 ('G.MetaCons name _1 _2) (G.S1 si (G.Rec0 a))) =
'[ '(name, a) ]
adtToRec (G.M1 (G.M1 (G.K1 a))) = UParamUnsafe
( symbolToMText @name
, lPackValue a
)
instance
TypeError ('Text "UParam linearization requires exactly one field \
\in each constructor") =>
GUParamLinearize (G.C1 i G.U1) where
type GUParamLinearized (G.C1 i G.U1) =
TypeError ('Text "Bad linearized ADT")
adtToRec = error "impossible"
instance
TypeError ('Text "UParam linearization requires exactly one field \
\in each constructor") =>
GUParamLinearize (G.C1 i (x :*: y)) where
type GUParamLinearized (G.C1 i (x :*: y)) =
TypeError ('Text "Bad linearized ADT")
adtToRec = error "impossible"
instance Typeable interface => TypeHasDoc (UParam interface) where
typeDocName _ = "Upgradable parameter"
typeDocMdReference p = customTypeDocMdReference ("UParam", DType p) []
typeDocMdDescription =
"This type encapsulates parameter for one of entry points. \
\It keeps entry point name and corresponding argument serialized."
typeDocHaskellRep = homomorphicTypeDocHaskellRep
typeDocMichelsonRep = homomorphicTypeDocMichelsonRep
pbsUParam :: forall ctorName. KnownSymbol ctorName => ParamBuildingStep
pbsUParam =
let ctor = build $ symbolValT' @ctorName
in ParamBuildingStep
{ pbsEnglish =
"Wrap into *UParam* as " <> mdTicked ctor <> " entrypoint."
, pbsHaskell =
\a -> "mkUParam #" <> ctor <> " (" <> a <> ")"
, pbsMichelson =
\a -> "Pair \"" <> ctor <> "\" (pack (" <> a <> "))"
}