lorentz-0.13.1: src/Lorentz/Address.hs
-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ
{- |
This module introduces several types for safe work with @address@ and
@contract@ types. All available types for that are represented in the following
table:
+------------------------+------------+-------------------+----------------------+
| Type | Type safe? | What it refers to | Michelson reflection |
+========================+============+===================+======================+
| Address | No | Whole contract | address |
+------------------------+------------+-------------------+----------------------+
| EpAddress | No | Entrypoint | address |
+------------------------+------------+-------------------+----------------------+
| TAddress | Yes | Whole contract | address |
+------------------------+------------+-------------------+----------------------+
| FutureContract | Yes | Entrypoint | address |
+------------------------+------------+-------------------+----------------------+
| ContractRef | Yes | Entrypoint | contract |
+------------------------+------------+-------------------+----------------------+
This module also provides functions for converting between these types in Haskell
and Michelson worlds.
In the latter you can additionally use coercions and dedicated instructions from
"Lorentz.Instr".
-}
module Lorentz.Address
( TAddress (..)
, FutureContract (..)
-- ** Conversions
, asAddressOf
, asAddressOf_
, callingAddress
, callingDefAddress
, callingTAddress
, callingDefTAddress
, ToAddress (..)
, ToTAddress (..)
, ToTAddress_
, toTAddress_
, ToContractRef (..)
, FromContractRef (..)
, convertContractRef
-- * Re-exports
, Address
, EpAddress (..)
, ContractRef (..)
, M.coerceContractRef
) where
import Data.Type.Bool (Not, type (&&))
import Fmt (Buildable)
import Lorentz.Annotation
import Lorentz.Base
import Lorentz.Constraints
import qualified Lorentz.Entrypoints.Core as Ep
import Morley.Michelson.Typed (ContractRef(..), IsoValue(..))
import qualified Morley.Michelson.Typed as M
import Morley.Michelson.Typed.Entrypoints (EpAddress(..))
import Morley.Tezos.Address (Address)
import Morley.Util.Type
import Morley.Util.TypeLits
-- | Address which remembers the parameter and views types of the contract
-- it refers to.
--
-- It differs from Michelson's @contract@ type because it cannot contain
-- entrypoint, and it always refers to entire contract parameter even if this
-- contract has explicit default entrypoint.
newtype TAddress (p :: Type) (vd :: Type) = TAddress { unTAddress :: Address }
deriving stock (Generic, Show)
deriving newtype (Eq, Ord, Buildable)
deriving anyclass (IsoValue, HasAnnotation)
-- | For a contract and an address of its instance, construct a typed address.
asAddressOf :: contract cp st vd -> Address -> TAddress cp vd
asAddressOf _ = TAddress
asAddressOf_ :: contract cp st vd -> Address : s :-> TAddress cp vd : s
asAddressOf_ _ = I M.Nop
-- | Turn 'TAddress' to 'ContractRef' in /Haskell/ world.
--
-- This is an analogy of @address@ to @contract@ convertion in Michelson world,
-- thus you have to supply an entrypoint (or call the default one explicitly).
callingTAddress
:: forall cp vd mname.
(NiceParameterFull cp)
=> TAddress cp vd
-> Ep.EntrypointRef mname
-> ContractRef (Ep.GetEntrypointArgCustom cp mname)
callingTAddress = callingAddress @cp @vd
{-# DEPRECATED callingTAddress "Use `callingAddress`" #-}
-- | Specification of 'callingTAddress' to call the default entrypoint.
callingDefTAddress
:: forall cp vd.
(NiceParameterFull cp)
=> TAddress cp vd
-> ContractRef (Ep.GetDefaultEntrypointArg cp)
callingDefTAddress taddr = callingTAddress taddr Ep.CallDefault
{-# DEPRECATED callingDefTAddress "Use `callingDefAddress`" #-}
-- | Generalization of 'callingTAddress' to any typed address.
callingAddress
:: forall cp vd addr mname.
(ToTAddress cp vd addr, NiceParameterFull cp)
=> addr
-> Ep.EntrypointRef mname
-> ContractRef (Ep.GetEntrypointArgCustom cp mname)
callingAddress (toTAddress @cp @vd -> TAddress addr) epRef =
withDict (niceParameterEvi @cp) $
case Ep.parameterEntrypointCallCustom @cp epRef of
epc@M.EntrypointCall{} -> ContractRef addr (M.SomeEpc epc)
-- | Generalization of 'callingDefTAddress' to any typed address.
callingDefAddress
:: forall cp vd addr.
(ToTAddress cp vd addr, NiceParameterFull cp)
=> addr
-> ContractRef (Ep.GetDefaultEntrypointArg cp)
callingDefAddress addr = callingAddress @cp @vd addr Ep.CallDefault
-- | Something coercible to 'TAddress cp'.
type ToTAddress_ cp vd addr = (ToTAddress cp vd addr, ToT addr ~ ToT Address)
-- | Cast something appropriate to 'TAddress'.
toTAddress_
:: forall cp addr vd s.
(ToTAddress_ cp vd addr)
=> addr : s :-> TAddress cp vd : s
toTAddress_ = I M.Nop
-- | Address associated with value of @contract arg@ type.
--
-- Places where 'ContractRef' can appear are now severely limited,
-- this type gives you type-safety of 'ContractRef' but still can be used
-- everywhere.
-- This type is not a full-featured one rather a helper; in particular, once
-- pushing it on stack, you cannot return it back to Haskell world.
--
-- Note that it refers to an entrypoint of the contract, not just the contract
-- as a whole. In this sense this type differs from 'TAddress'.
--
-- Unlike with 'ContractRef', having this type you still cannot be sure that
-- the referred contract exists and need to perform a lookup before calling it.
newtype FutureContract arg = FutureContract { unFutureContract :: ContractRef arg }
instance IsoValue (FutureContract arg) where
type ToT (FutureContract arg) = ToT EpAddress
toVal (FutureContract contract) = toVal $ M.contractRefToAddr contract
fromVal = error "Fetching 'FutureContract' back from Michelson is impossible"
instance HasAnnotation (FutureContract a) where
getAnnotation _ = M.starNotes
-- | Convert something to 'Address' in /Haskell/ world.
--
-- Use this when you want to access state of the contract and are not interested
-- in calling it.
class ToAddress a where
toAddress :: a -> Address
instance ToAddress Address where
toAddress = id
instance ToAddress EpAddress where
toAddress = eaAddress
instance ToAddress (TAddress cp vd) where
toAddress = unTAddress
instance ToAddress (FutureContract cp) where
toAddress = toAddress . unFutureContract
instance ToAddress (ContractRef cp) where
toAddress = crAddress
-- | Convert something referring to a contract (not specific entrypoint)
-- to 'TAddress' in /Haskell/ world.
class ToTAddress (cp :: Type) (vd :: Type) (a :: Type) where
toTAddress :: a -> TAddress cp vd
instance ToTAddress cp vd Address where
toTAddress = TAddress
instance (cp ~ cp', vd ~ vd') => ToTAddress cp vd (TAddress cp' vd') where
toTAddress = id
-- | Convert something to 'ContractRef' in /Haskell/ world.
class ToContractRef (cp :: Type) (contract :: Type) where
toContractRef :: HasCallStack => contract -> ContractRef cp
instance (cp ~ cp') => ToContractRef cp (ContractRef cp') where
toContractRef = id
instance (NiceParameter cp, cp ~ cp') => ToContractRef cp (FutureContract cp') where
toContractRef = unFutureContract
instance ( FailWhen cond msg
, cond ~
( Ep.CanHaveEntrypoints cp &&
Not (Ep.ParameterEntrypointsDerivation cp == Ep.EpdNone)
)
, msg ~
( 'Text "Cannot apply `ToContractRef` to `TAddress`" ':$$:
'Text "Consider using call(Def)TAddress first`" ':$$:
'Text "(or if you know your parameter type is primitive," ':$$:
'Text " make sure typechecker also knows about that)" ':$$:
'Text "For parameter `" ':<>: 'ShowType cp ':<>: 'Text "`"
)
, cp ~ arg, NiceParameter arg
-- These constraints should naturally derive from ones above,
-- but proving that is not worth the effort
, NiceParameterFull cp, Ep.GetDefaultEntrypointArg cp ~ cp
) =>
ToContractRef arg (TAddress cp vd) where
toContractRef = callingDefTAddress
-- | Convert something from 'ContractRef' in /Haskell/ world.
class FromContractRef (cp :: Type) (contract :: Type) where
fromContractRef :: ContractRef cp -> contract
instance (cp ~ cp') => FromContractRef cp (ContractRef cp') where
fromContractRef = id
instance (cp ~ cp') => FromContractRef cp (FutureContract cp') where
fromContractRef = FutureContract . fromContractRef
instance FromContractRef cp EpAddress where
fromContractRef = M.contractRefToAddr
instance FromContractRef cp Address where
fromContractRef = crAddress
convertContractRef
:: forall cp contract2 contract1.
(ToContractRef cp contract1, FromContractRef cp contract2)
=> contract1 -> contract2
convertContractRef = fromContractRef @cp . toContractRef