4. On-Chain

The on-chain side of the server implementation consists of validators and minting policies. In our example, the sender locks into a script UTxO the tokens to be paid to the receiver, containing in the datum all the necessary information. In order to check that it is valid, a control token is minted in the same transaction, so validation can be performed by running the corresponding minting policy. For canceling or resolving it is necessary to spend the script UTxO, so the corresonding validator performs all the necessary checks.

In the OnChain module we find the Haskell implementation of the validator and the minting policy as boolean functions. In the Validator module we define mostly boilerplate code for compiling to Plutus.

4.1. OnChain Module

Because all the functions on this module will be compiled to Plutus we must use the Plutus Prelude instead of the Haskell Prelude. To avoid confusion about which Prelude we are using, we add the NoImplicitPrelude pragma at the top of the module. As a result we don’t have any of the common types and functions in this scope. Thus we must explictly import from the Prelude all we need:

import PlutusTx.Prelude ( Integer, Bool
                        , ($), (&&), (||), (==)
                        , traceIfFalse
                        )

4.1.1. Minting Policy

Starting an escrow involves minting a control token, thus running the validation implemented on its minting policy. We use this fact to ensure some good starting properties on the script UTxO we are creating. A similar approach is proposed in this blogpost. For that, we have to distinguish if we are minting or burning by checking the information in the script context: if the amount of minted tokens is -1 we are burning, if it is 1 we are minting. If the transaction mints a different amount of tokens, the minting policy fails and so does the start operation. If we are minting the control token, then we have to check that it is paid to the escrow script and that the datum is correct:

{-# INLINABLE mkControlTokenMintingPolicy #-}
mkControlTokenMintingPolicy :: ScriptAddress -> () -> ScriptContext -> Bool
mkControlTokenMintingPolicy addr _ ctx =
    traceIfFalse "Burning less or more than one control token" (mintedA == -1)
        ||
        (   traceIfFalse "Minting more than one control token"
                         (mintedA == 1)
         && traceIfFalse "The control token was not paid to the script address"
                         controlTokenPaid
         && traceIfFalse "Wrong information in Datum"
                         correctDatum
        )
  where
    ....

The minting policy is parameterized on the script address of the escrow UTxO we are creating, so we know where the control token must go. We check that it is paid to the script UTxO in controlTokenPaid:

controlTokenPaid :: Bool
controlTokenPaid =
    assetClassValueOf (txOutValue escrowUtxo) (assetClass mintedCS mintedTN)
    ==
    mintedA

escrowUtxo :: TxOut
escrowUtxo = getSingleton $ outputsAt addr info

mintedCS :: CurrencySymbol
mintedTN :: TokenName
mintedA :: Integer
(mintedCS, mintedTN, mintedA) = getSingleton $
                                flattenValue $ txInfoMint info

Here, the escrow UTxO output is obtained using outputsAt, and with the utility getSingleton we get it from the list or fail if there is more than one output at the same address. Also, the minting information is extracted by using flattenValue together with getSingleton, failing if it is found that more than one asset is being minted.

To ensure that the datum is correct we need to check that the sender address corresponds to the transaction signer’s payment key, the amount of tokens to receive is greater than zero, and the control token Asset Class is the one that is being minted.

correctDatum :: Bool
correctDatum =
    traceIfFalse "The signer is not the sender on the escrow"
                 correctSigner
 && traceIfFalse "The asset minted does not match with the control token"
                 correctControlAssetClass
 && traceIfFalse "The receive amount of tokens to exchange is not positive"
                 correctAmount

For implementing these three checks we simply read the script UTxO datum and compare its information with the expected one.

4.1.2. Validator

The on-chain validator, as we briefly mentioned, is parameterized on the receiver address. This design decision allows us to have a unique script address for each ReceiverAddress. Given that we are minting a control token, it would be desired to include in the parameter its asset class for checking that it is burned when canceling or resolving. However, we can’t do this because of a circularity problem: in the minting policy we need the script address for ensuring that the token is paid to the corresponding UTxO. We solve this issue by including the control token asset class in the datum, as we showed before.

The validator will run when the script UTxO is spent, and it corresponds to Cancel and Resolve operations, which are the only two constructors of EscrowRedeemer type. In both cases we have to check that the control token is burned and only one script UTxO is spent. The latter check is important for preventing double satisfaction attacks.

{-# INLINABLE mkEscrowValidator #-}
mkEscrowValidator :: ReceiverAddress
                  -> EscrowDatum
                  -> EscrowRedeemer
                  -> ScriptContext
                  -> Bool
mkEscrowValidator raddr EscrowDatum{..} r ctx =
    case r of
        CancelEscrow  -> cancelValidator eInfo signer
        ResolveEscrow -> resolveValidator info eInfo raddr signer scriptValue
   &&
   traceIfFalse "more than one script input utxo"
                (length sUtxos == 1)
   &&
   traceIfFalse "controlToken was not burned"
                (eAssetClass == assetClass mintedCS mintedTN && mintedA == -1)
  where
    info :: TxInfo
    info = scriptContextTxInfo ctx

    signer :: PubKeyHash
    signer = getSingleton $ txInfoSignatories info

    scriptValue :: Value
    scriptValue = txOutValue (getSingleton sUtxos)
                <> assetClassValue eAssetClass (-1)

    .....

We modularize the validator implementing functions for each case: cancelValidator and resolveValidator. For implementing the first one we need the Escrow Info (which is inside the datum) and the signer (which is extracted from the Script Context). For implementing the second one we also pass the entire Script Context info, the validator parameter (the receiver address), and the value contained inside the script utxo that must be paid to the receiver address (it’s the whole value less the control token, which is burned).

Validating a cancel operation is simple: we have to check that the escrow sender is the one signing the transaction.

{-# INLINABLE cancelValidator #-}
cancelValidator :: EscrowInfo -> PubKeyHash -> Bool
cancelValidator EscrowInfo{..} signer =
    traceIfFalse "cancelValidator: Wrong sender signature"
                 $ signerIsSender signer sender

The sender address is stored in the datum at start, so at canceling we check that the information in the datum coincides with the transaction signer.

A more interesting validation is required for resolving an escrow. We check that the signer is the receiver, and payments to sender and receiver addresses are correct.

{-# INLINABLE resolveValidator #-}
resolveValidator
    :: TxInfo
    -> EscrowInfo
    -> ReceiverAddress
    -> PubKeyHash
    -> Value
    -> Bool
resolveValidator info ei raddr@ReceiverAddress{..} signer scriptValue =
    traceIfFalse "resolveValidator: Wrong receiver signature"
                 (signerIsReceiver signer raddr)
    &&
    traceIfFalse "resolveValidator: Wrong sender's payment"
                 (valueToSender ei `leq` senderV)
    &&
    traceIfFalse "resolveValidator: Wrong receiver's payment"
                 (scriptValue `leq` receiverV)
  where
    senderV :: Value
    senderV = valuePaidTo (eInfoSenderAddr ei) info

    receiverV :: Value
    receiverV = valuePaidTo (toAddress rAddr) info

We need the Script Context info for reading the value that is being paid in this transaction, and the validator parameter for knowing the receiver address. Notice that we use the business logic function valueToSender for computing the (minimum) value that should be paid to the sender.

4.2. Validator Module

The content of the Validator module is mainly boilerplate. It corresponds to the compilation of the validator and minting policy, from Haskell to Plutus.

For compiling the minting policy, we need to convert the boolean function mkControlTokenMintingPolicy into a compiled MintingPolicy.

controlTokenMP :: ScriptAddress -> MintingPolicy
controlTokenMP saddr =
    mkMintingPolicyScript $
    $$(compile [|| mkUntypedMintingPolicy . mkControlTokenMintingPolicy ||])
    `applyCode`
    liftCode saddr

Whithout going into details, controlTokenMP compiles our boolean function to Plutus, obtaining a MintingPolicy. For that, it first generates an untyped version of our function, and then compiles it. Given that mkControlTokenMintingPolicy receives a parameter, it must be compiled too, by calling liftCode function.

Obtaining the resulting currency symbol is straightforward

controlTokenCurrency :: ScriptAddress -> CurrencySymbol
controlTokenCurrency = scriptCurrencySymbol . controlTokenMP

Let’s now review how to compile the main validator. It’s slightly different to the minting policy. First we need to indicate which types correspond to Datum and Redeemer, by defining an empty data type and then instantiating ValidatorTypes typeclass

data Escrowing
instance ValidatorTypes Escrowing where
    type instance DatumType    Escrowing = EscrowDatum
    type instance RedeemerType Escrowing = EscrowRedeemer

Then we compile our boolean function mkEscrowValidator to a TypedValidator

escrowInst :: ReceiverAddress -> TypedValidator Escrowing
escrowInst raddr =
    mkTypedValidator @Escrowing
    ($$(compile [|| mkEscrowValidator ||])
        `applyCode`
        liftCode raddr
    )
    $$(compile [|| mkUntypedValidator @EscrowDatum @EscrowRedeemer ||])

Finally we obtain the Validator and ScriptAddress, that are needed in the off-chain code for building the transactions

escrowValidator :: ReceiverAddress -> Validator
escrowValidator = validatorScript . escrowInst

escrowAddress :: ReceiverAddress -> ScriptAddress
escrowAddress = mkValidatorAddress . escrowValidator