Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
This module contains a type family for converting a type to its RPC representation, and TemplateHaskell functions for deriving RPC representations for custom types.
Synopsis
- type family TAsRPC t where ...
- class TAsRPC (ToT t) ~ ToT (AsRPC t) => HasRPCRepr (t :: Type) where
- deriveRPCWithOptions :: String -> DeriveRPCOptions -> Q [Dec]
- data DeriveRPCOptions = DeriveRPCOptions {}
- deriveRPC :: String -> Q [Dec]
- valueAsRPC :: HasCallStack => Value t -> Value (TAsRPC t)
- replaceBigMapIds :: forall t m. Monad m => (forall k v. (SingI k, SingI v) => Natural -> m (Value ('TBigMap k v))) -> Sing t -> Value (TAsRPC t) -> m (Value t)
- notesAsRPC :: Notes t -> Notes (TAsRPC t)
- rpcSingIEvi :: forall t. SingI t => Dict (SingI (TAsRPC t))
- rpcHasNoOpEvi :: forall (t :: T). (SingI t, ForbidOp t) => Dict (ForbidOp (TAsRPC t))
- rpcHasNoBigMapEvi :: forall (t :: T). (SingI t, ForbidBigMap t) => Dict (ForbidBigMap (TAsRPC t))
- rpcHasNoNestedBigMapsEvi :: forall (t :: T). (SingI t, ForbidNestedBigMaps t) => Dict (ForbidNestedBigMaps (TAsRPC t))
- rpcHasNoContractEvi :: forall (t :: T). (SingI t, ForbidContract t) => Dict (ForbidContract (TAsRPC t))
- rpcStorageScopeEvi :: forall (t :: T). StorageScope t => Dict (StorageScope (TAsRPC t))
Documentation
type family TAsRPC t where ... Source #
A type-level function that maps a Michelson type to its Tezos RPC representation.
For example, when we retrieve a contract's storage using the Tezos RPC,
all its big_map
s will be replaced by nat
, representing a big_map ID.
>>>
:k! TAsRPC ('TBigMap 'TInt 'TString)
... = 'TNat
>>>
:k! TAsRPC ('TList ('TBigMap 'TInt 'TString))
... = 'TList 'TNat
>>>
:k! TAsRPC ('TPair 'TString ('TPair 'TAddress ('TBigMap 'TInt 'TString)))
... = 'TPair 'TString ('TPair 'TAddress 'TNat)
NB: As far as we are aware, details of RPC representation of Michelson
types are not documented. We know empirically that big_map
s are
represented as their ids, and are the only type with an explicitly
different representation.
Whether TAsRPC
needs to propagate into type parameters then depends on
whether a value can hold big_map values.
- Values of type
option a
,list a
,pair a b
, andor a b
can contain big_map values, so their RPC representations areoption (TAsRPC a)
,list (TAsRPC a)
,pair (TAsRPC a) (TAsRPC b)
andor (TAsRPC a) (TAsRPC b)
. - The keys of a
map k v
cannot be big_maps, but the values can, so its RPC representation ismap k (TAsRPC v)
. - Values of type
set a
cannot contain big_maps, so its RPC representation is justset a
. - Values of type
contract a
cannot contain big_maps either, because it's just a wrapper for an address and an entrypoint name, so its RPC representation is justcontract a
. The same reasoning applies toticket a
andlambda a b
.
class TAsRPC (ToT t) ~ ToT (AsRPC t) => HasRPCRepr (t :: Type) Source #
A type-level function that maps a type to its Tezos RPC representation.
For example, when we retrieve a contract's storage using the Tezos RPC, all its BigMap
s will be replaced
by BigMapId
s.
So if a contract has a storage of type T
, when we call the Tezos RPC
to retrieve it, we must deserialize the micheline expression to the type AsRPC T
.
AsRPC (BigMap Integer MText) ~ BigMapId Integer MText AsRPC [BigMap Integer MText] ~ [BigMapId Integer MText] AsRPC (MText, (Address, BigMap Integer MText)) ~ (MText, (Address, BigMapId Integer MText))
The following law must hold:
TAsRPC (ToT t) ~ ToT (AsRPC t)
In other words, ToT
and AsRPC
/TAsRPC
must be commutative.
Storage ----------(applying ToT)-------------> ToT Storage | | | | | | (applying AsRPC) (applying TAsRPC) | | | | | | | V | TAsRPC (ToT Storage) V ~ AsRPC Storage ------(applying ToT)-----------> ToT (AsRPC Storage)
This law ensures that we can go from some type Storage
to AsRPC Storage
by
composing fromVal . valueAsRPC . toVal
.
Storage ------------(toVal)--------------> Value (ToT Storage) | | | | | | (fromVal . valueAsRPC . toVal) (valueAsRPC) | | | | | | | V | Value (TAsRPC (ToT Storage)) V ~ AsRPC Storage <--------(fromVal)--------- Value (ToT (AsRPC Storage))
Instances
deriveRPCWithOptions :: String -> DeriveRPCOptions -> Q [Dec] Source #
Derive an RPC representation for a type, as well as instances for
Generic
, IsoValue
, HasRPCRepr
and optionally HasAnnotation
.
>>>
:{
data ExampleStorage a b = ExampleStorage { esField1 :: Integer , esField2 :: [BigMap Integer MText] , esField3 :: a } deriving stock Generic deriving anyclass IsoValue -- deriveRPC "ExampleStorage" :}
Will generate:
>>>
:i ExampleStorageRPC
... data ExampleStorageRPC a b = ExampleStorageRPC {esField1RPC :: !(AsRPC Integer), esField2RPC :: !(AsRPC [BigMap Integer MText]), esField3RPC :: !(AsRPC a)} ... instance forall a k (b :: k). IsoValue (AsRPC a) => IsoValue (ExampleStorageRPC a b) ... instance forall a k (b :: k). Generic (ExampleStorageRPC a b) ...
>>>
:i HasRPCRepr
... instance forall a k (b :: k). HasRPCRepr a => HasRPCRepr (ExampleStorage a b) ...
>>>
:i AsRPC
... type instance forall k a (b :: k). AsRPC (ExampleStorage a b) = ExampleStorageRPC a b ...
When droHasAnnotation
is True
(the default), it will also generate a
HasAnnotation
(from Lorentz
) instance like:
>>>
:i HasAnnotation
... instance forall a k (b :: k). With '[HasAnnotation, HasRPCRepr] (ExampleStorage a b) => HasAnnotation (ExampleStorageRPC a b) ...
Note that if the type doesn't contain type variables or only contains phantom
type variables, HasRPCRepr
constraint is omitted, as it would be redundant.
HasAnnotation
and its methods must be in scope.
Void types will generate a type synonym instead, e.g.
>>>
:{
data MyVoidType deriving stock Generic deriving anyclass IsoValue -- deriveRPC "MyVoidType" :}
will produce
>>>
:i AsRPC
... type instance AsRPC MyVoidType = MyVoidTypeRPC ...
>>>
:i MyVoidTypeRPC
... type MyVoidTypeRPC = MyVoidType ...
When droRecursive
is True
, recursively enumerate data
, newtype
and
type
declarations, and derive an RPC representation for each type that doesn't
yet have one. This will however silently skip over void types that do not have
an IsoValue
instance, which is usually what you want, but be mindful of this.
You can also pass in a list of types for which you _don't_ want an RPC
representation to be derived in droRecursiveSkipTypes
. You may need this if
you're using non-void types that don't have an IsoValue
instance as phantom
types somewhere. The algorithm isn't smart enough to figure out those don't need
RPC representation and will try to derive it anyway.
>>>
:{
data B = B deriving (Generic, IsoValue) data C = C deriving (Generic, IsoValue) data D = D deriving (Generic, IsoValue) data E a = E deriving (Generic, IsoValue) -- instance HasRPCRepr D where type AsRPC D = () -- data A = A B (E C) D deriving (Generic, IsoValue) deriveRPCWithOptions "A" def{droRecursive=True, droRecursiveSkipTypes=["C"]} :}
In this example, this will generate an RPC representation for A
and B
,
>>>
:i ARPC
... data ARPC = ... ...>>>
:i BRPC
... data BRPC = ... ...
but not for C
(because we explicitly said we don't want one) or D
(because
it already has one).
>>>
:i CRPC
... ... Not in scope: ... ...>>>
:i DRPC
... ... Not in scope: ... ...
When using with droRecursive = False
, if some types do not have HasRPCRepr
,
IsoValue
or Generic
instances, but need to, an error will be raised:
>>>
:{
data B = B deriving (Generic, IsoValue) data A = A B deriving (Generic, IsoValue) -- deriveRPCWithOptions "A" def{droRecursive = False} :} ... ... error: ... Type ... must implement 'HasRPCRepr'.
>>>
:{
data B = B deriving (Generic, IsoValue) data A = A B deriving (Generic, IsoValue) -- deriveRPCWithOptions "B" def{droRecursive = False} deriveRPCWithOptions "A" def{droRecursive = False} :}
>>>
:i AsRPC
... type instance AsRPC B = BRPC ... type instance AsRPC A = ARPC ... ...
This check isn't very smart, so it might miss some corner cases.
data DeriveRPCOptions Source #
Options for deriveRPCWithOptions
.
DeriveRPCOptions | |
|
Instances
Default DeriveRPCOptions Source # | |
Defined in Morley.AsRPC def :: DeriveRPCOptions # |
deriveRPC :: String -> Q [Dec] Source #
deriveRPCWithOptions
using default DeriveRPCOptions
.
Conversions
valueAsRPC :: HasCallStack => Value t -> Value (TAsRPC t) Source #
Replace all big_maps in a value with the respective big_map IDs.
Throws an error if it finds a big_map without an ID.
:: forall t m. Monad m | |
=> (forall k v. (SingI k, SingI v) => Natural -> m (Value ('TBigMap k v))) | A function for looking up a bigmap using its ID. |
-> Sing t | |
-> Value (TAsRPC t) | |
-> m (Value t) |
Replaces all bigmap IDs with their corresponding bigmap values.
This is the inverse of valueAsRPC
.
notesAsRPC :: Notes t -> Notes (TAsRPC t) Source #
Replace all big_map
annotations in a value with nat
annotations.
Entailments
rpcSingIEvi :: forall t. SingI t => Dict (SingI (TAsRPC t)) Source #
A proof that if a singleton exists for t
,
then so it does for TAsRPC t
.
rpcHasNoOpEvi :: forall (t :: T). (SingI t, ForbidOp t) => Dict (ForbidOp (TAsRPC t)) Source #
A proof that if t
does not contain any operations, then neither does TAsRPC t
.
rpcHasNoBigMapEvi :: forall (t :: T). (SingI t, ForbidBigMap t) => Dict (ForbidBigMap (TAsRPC t)) Source #
A proof that AsRPC (Value t)
does not contain big_maps.
rpcHasNoNestedBigMapsEvi :: forall (t :: T). (SingI t, ForbidNestedBigMaps t) => Dict (ForbidNestedBigMaps (TAsRPC t)) Source #
A proof that AsRPC (Value t)
does not contain nested big_maps.
rpcHasNoContractEvi :: forall (t :: T). (SingI t, ForbidContract t) => Dict (ForbidContract (TAsRPC t)) Source #
A proof that if t
does not contain any contract values, then neither does TAsRPC t
.
rpcStorageScopeEvi :: forall (t :: T). StorageScope t => Dict (StorageScope (TAsRPC t)) Source #
A proof that if t
is a valid storage type, then so is TAsRPC t
.