packages feed

cleveland-0.1.0: lorentz-test/Test/Lorentz/Tickets/Contracts.hs

-- SPDX-FileCopyrightText: 2021 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ

{-# LANGUAGE RebindableSyntax #-}
{-# LANGUAGE NoApplicativeDo #-}
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
{-# OPTIONS_GHC -Wno-orphans #-}

-- | Contracts for tickets tests.
module Test.Lorentz.Tickets.Contracts
  ( atomicMissileLaunchContract

  , PermitTokensStorage (..)
  , PermitTokensParameter
  , permitTokensContract

  , allowancesContract
  ) where

import Prelude (Typeable)

import Lorentz
import Lorentz.Tickets

-- Errors
----------------------------------------------------------------------------

type instance ErrorArg "insufficient_tokens_permitted_by_ticket" =
  ("permitted" :! Natural, "spent" :! Natural)

instance CustomErrorHasDoc "insufficient_tokens_permitted_by_ticket" where
  customErrClass = ErrClassActionException
  customErrDocMdCause =
    "Attempt to spend more tokens than permitted."

----------------------------------------------------------------------------
-- Contract with authorized action
----------------------------------------------------------------------------

atomicMissileLaunchContract
  :: (Typeable ticketer)
  => TAddress ticketer () -> Contract (Ticket Integer) Bool ()
atomicMissileLaunchContract ticketer = defaultContract $ do
  car; push ticketer
  authorizeAction validateCode
  push True; nil; pair
  where
    validateCode =
      push 12345 # eq # if_ nop (failUsing [mt|Bad code|])

----------------------------------------------------------------------------
-- Contract with authorized tokens spend from single address
----------------------------------------------------------------------------

data PermitTokensStorage = PermitTokensStorage
  { ptsPermittedTokens :: Maybe (STicket "tokens" ())
  , ptsAdmin :: Address
    -- ^ Who permits tokens spending
  } deriving stock (Generic)
    deriving anyclass (IsoValue, HasAnnotation)

instance TypeHasDoc PermitTokensStorage where
  typeDocMdDescription = "Storage"

-- TODO: [#585]: avoid uses of next two functions, work with storage
-- as with normal ADT
decomposePermitTokensStorage
  :: PermitTokensStorage : s
  :-> Maybe (STicket "tokens" ()) : Address : s
decomposePermitTokensStorage = forcedCoerce_ # unpair

composePermitTokensStorage
  :: Maybe (STicket "tokens" ()) : Address : s
  :-> PermitTokensStorage : s
composePermitTokensStorage = pair # forcedCoerce_

instance HasFieldOfType PermitTokensStorage name ty =>
         StoreHasField PermitTokensStorage name ty where
  storeFieldOps = storeFieldOpsADT

data PermitTokensParameter
  = PermitSpend Natural
  | PermitAllow (Ticket ())
  | PermitGet (Void_ () Natural)
  | PermitSetAdmin Address
  deriving stock (Generic)
  deriving anyclass (IsoValue)

instance ParameterHasEntrypoints PermitTokensParameter where
  type ParameterEntrypointsDerivation PermitTokensParameter = EpdPlain

-- | A contract that emulates simple allowance functionality, where
-- permission is provided via tickets.
permitTokensContract
  :: Contract PermitTokensParameter PermitTokensStorage ()
permitTokensContract = defaultContract $ do
  doc $ dStorage @PermitTokensStorage
  unpair; caseT
    ( #cPermitSpend /-> do
        dip decomposePermitTokensStorage
        subtractSTicket (failCustom #insufficient_tokens_permitted_by_ticket)
        composePermitTokensStorage
    , #cPermitAllow /-> do
        dip decomposePermitTokensStorage
        dupN @3; checkedCoerce_ @Address
        dip toSTicket; verifyTicketer; drop @()
        addSTicket
        composePermitTokensStorage
    , #cPermitGet /-> void_ do
        drop @()
        stToField #ptsPermittedTokens
        sTicketAmount
    , #cPermitSetAdmin /-> do
        dip $ drop @PermitTokensStorage
        none
        composePermitTokensStorage
    )
  nil; pair

----------------------------------------------------------------------------
-- Contract with authorized tokens spend from many addresses
----------------------------------------------------------------------------

type AllowancesStorage =
  BigMap Address (STicket "allowances" ())

data AllowancesParameter
  = AllowancesSpend (Address, Natural)
  | AllowancesAllow (Ticket ())
  | AllowancesGet (Void_ Address Natural)
  deriving stock (Generic)
  deriving anyclass (IsoValue, HasAnnotation)

instance ParameterHasEntrypoints AllowancesParameter where
  type ParameterEntrypointsDerivation AllowancesParameter = EpdPlain

-- | A contract that emulates simple allowance functionality, where
-- permission is provided via tickets.
allowancesContract
  :: Contract AllowancesParameter AllowancesStorage ()
allowancesContract = defaultContract $ do
  unpair; caseT
    ( #cAllowancesSpend /-> do
        unpair @Address @Natural; swap
        dupN @2 @Address
        dip @Address $ do
          dip @Natural $ do
            dip none; getAndUpdate
          subtractSTicket (failCustom #insufficient_tokens_permitted_by_ticket)
        update
    , #cAllowancesAllow /-> do
        toSTicket; dip (drop @())
        dip swap
        stackType @[Address, AllowancesStorage, STicket _ _]
        dup; dip @Address $ do
          dip none; getAndUpdate
          dig @2
          addSTicket
        update
    , #cAllowancesGet /-> void_ do
        get; sTicketAmount
    )
  nil; pair