183 lines
5.4 KiB
Haskell
183 lines
5.4 KiB
Haskell
{-# LANGUAGE PolyKinds #-}
|
|
{-# LANGUAGE StandaloneKindSignatures #-}
|
|
{-# LANGUAGE UndecidableInstances #-}
|
|
{-# OPTIONS_GHC -Wwarn=missing-methods #-}
|
|
{-# OPTIONS_GHC -Wwarn=unused-imports #-}
|
|
|
|
module Agora.SafeMoney (module Agora.SafeMoney) where
|
|
|
|
import Data.Proxy (Proxy (Proxy))
|
|
import Data.String
|
|
import GHC.TypeLits (
|
|
CmpNat,
|
|
KnownNat,
|
|
KnownSymbol,
|
|
Nat,
|
|
SomeNat (..),
|
|
SomeSymbol (..),
|
|
Symbol,
|
|
natVal,
|
|
someNatVal,
|
|
someSymbolVal,
|
|
symbolVal,
|
|
)
|
|
import Prelude
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
import Plutus.V1.Ledger.Value (AssetClass (..))
|
|
import Plutus.V1.Ledger.Value qualified as Ledger
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
import Plutarch.Api.V1
|
|
import Plutarch.Builtin
|
|
import Plutarch.Internal
|
|
import Plutarch.Prelude
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Type-level unique identifier for an AssetClass
|
|
type MoneyClass =
|
|
( -- AssetClass
|
|
Symbol
|
|
, -- TokenName
|
|
Symbol
|
|
, -- Decimal places
|
|
Nat
|
|
)
|
|
|
|
newtype Discrete (mc :: MoneyClass) (s :: S)
|
|
= Discrete (Term s PInteger)
|
|
deriving (PlutusType, PIsData, PEq, POrd) via (DerivePNewtype (Discrete mc) PInteger)
|
|
|
|
instance Num (Term s (Discrete mc)) where
|
|
(+) x y = pcon $
|
|
Discrete . unTermCont $ do
|
|
Discrete x' <- tcont $ pmatch x
|
|
Discrete y' <- tcont $ pmatch y
|
|
pure (x' + y')
|
|
abs x = pcon $
|
|
Discrete . unTermCont $ do
|
|
Discrete x' <- tcont $ pmatch x
|
|
pure (abs x')
|
|
negate x = pcon $
|
|
Discrete . unTermCont $ do
|
|
Discrete x' <- tcont $ pmatch x
|
|
pure (negate x')
|
|
(*) x y = pcon $
|
|
Discrete . unTermCont $ do
|
|
Discrete x' <- tcont $ pmatch x
|
|
Discrete y' <- tcont $ pmatch y
|
|
pure (x' * y')
|
|
fromInteger = error "Tried to `fromInteger` for a Discrete type. use `discrete` quasiquoter instead."
|
|
|
|
(^*) :: Term s (Discrete mc) -> Term s PInteger -> Term s (Discrete mc)
|
|
(^*) x y = pcon $
|
|
Discrete . unTermCont $ do
|
|
Discrete x' <- tcont $ pmatch x
|
|
pure (x' * y)
|
|
|
|
type LQ :: MoneyClass
|
|
type LQ = '("da8c30857834c6ae7203935b89278c532b3995245295456f993e1d24", "LQ", 6)
|
|
|
|
type ADA :: MoneyClass
|
|
type ADA = '("", "", 6)
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- TODO: upstream something like this
|
|
pfind' ::
|
|
PIsListLike list a =>
|
|
(Term s a -> Term s PBool) ->
|
|
Term s (list a :--> PMaybe a)
|
|
pfind' p =
|
|
precList
|
|
(\self x xs -> pif (p x) (pcon (PJust x)) (self # xs))
|
|
(const $ pcon PNothing)
|
|
|
|
-- TODO: upstream something like this
|
|
plookup ::
|
|
(PEq a, PIsListLike list (PBuiltinPair a b)) =>
|
|
Term s (a :--> list (PBuiltinPair a b) :--> PMaybe b)
|
|
plookup =
|
|
phoistAcyclic $
|
|
plam $ \k xs ->
|
|
pmatch (pfind' (\p -> pfstBuiltin # p #== k) # xs) $ \case
|
|
PNothing -> pcon PNothing
|
|
PJust p -> pcon (PJust (psndBuiltin # p))
|
|
|
|
-- This is quite silly.
|
|
plookupTuple ::
|
|
(PEq a, PIsListLike list (PAsData (PTuple a b)), PIsData a, PIsData b) =>
|
|
Term s (a :--> list (PAsData (PTuple a b)) :--> PMaybe b)
|
|
plookupTuple =
|
|
phoistAcyclic $
|
|
plam $ \k xs ->
|
|
pmatch (pfind' (\p -> (pfield @"_0" # pfromData p) #== k) # xs) $ \case
|
|
PNothing -> pcon PNothing
|
|
PJust p -> pcon (PJust (pfield @"_1" # pfromData p))
|
|
|
|
matchMaybe :: Term s r -> Term s (PMaybe a) -> TermCont @r s (Term s a)
|
|
matchMaybe r f = TermCont $ \k ->
|
|
pmatch f $ \case
|
|
PJust v -> k v
|
|
PNothing -> r
|
|
|
|
passetClassValueOf ::
|
|
Term s (PCurrencySymbol :--> PTokenName :--> PValue :--> PInteger)
|
|
passetClassValueOf =
|
|
phoistAcyclic $
|
|
plam $ \sym token value'' -> unTermCont $ do
|
|
PValue value' <- tcont $ pmatch value''
|
|
PMap value <- tcont $ pmatch value'
|
|
m' <- matchMaybe 0 (plookup # pdata sym # value)
|
|
PMap m <- tcont (pmatch (pfromData m'))
|
|
v <- matchMaybe 0 (plookup # pdata token # m)
|
|
pure (pfromData v)
|
|
|
|
passetClassValueOf' :: AssetClass -> Term s (PValue :--> PInteger)
|
|
passetClassValueOf' (AssetClass (sym, token)) =
|
|
passetClassValueOf # pconstant sym # pconstant token
|
|
|
|
-- | Downcast a 'PValue' to a 'Discrete' unit
|
|
valueDiscrete ::
|
|
forall (moneyClass :: MoneyClass) (ac :: Symbol) (n :: Symbol) (scale :: Nat) s.
|
|
( KnownSymbol ac
|
|
, KnownSymbol n
|
|
, moneyClass ~ '(ac, n, scale)
|
|
) =>
|
|
Term s (PValue :--> Discrete moneyClass)
|
|
valueDiscrete = phoistAcyclic $
|
|
plam $ \f ->
|
|
pcon . Discrete $
|
|
passetClassValueOf # (pconstant $ fromString $ symbolVal $ Proxy @ac)
|
|
# (pconstant $ fromString $ symbolVal $ Proxy @n)
|
|
# f
|
|
|
|
-- NOTE: discreteValue after valueDiscrete is loses information
|
|
|
|
-- | Get a 'PValue' from a 'Discrete'
|
|
discreteValue ::
|
|
forall (moneyClass :: MoneyClass) (ac :: Symbol) (n :: Symbol) (scale :: Nat) s.
|
|
( KnownSymbol ac
|
|
, KnownSymbol n
|
|
, moneyClass ~ '(ac, n, scale)
|
|
) =>
|
|
Term s (Discrete moneyClass :--> PValue)
|
|
discreteValue = phoistAcyclic $
|
|
plam $ \f -> pmatch f $ \case
|
|
Discrete p ->
|
|
psingletonValue
|
|
# (pconstant $ fromString $ symbolVal $ Proxy @ac)
|
|
# (pconstant $ fromString $ symbolVal $ Proxy @n)
|
|
# p
|
|
|
|
-- | Create a value with a single asset class
|
|
psingletonValue :: forall s. Term s (PCurrencySymbol :--> PTokenName :--> PInteger :--> PValue)
|
|
psingletonValue = phoistAcyclic $
|
|
plam $ \sym tok int ->
|
|
let innerTup = pcon $ PMap $ psingleton #$ ppairDataBuiltin # pdata tok # pdata int
|
|
outerTup = pcon $ PMap $ psingleton #$ ppairDataBuiltin # pdata sym # pdata innerTup
|
|
res = pcon $ PValue outerTup
|
|
in res
|