8

I want to be able to make calls to my custom native token module inside the validator script as I do with the Ada.hs module, but if I try I get a "E042:Error: Unsupported feature: Type constructor: GHC.Prim.ByteArray#" Error. Details below:

Currently I am trying to build a simple contract that once funded with X initial amount of X currency, the contract burn one coin each time an action is executed. For this I created a simple module for my dummy currency similar to the Ada module in Plutus.Ledger:

{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE DeriveAnyClass    #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE DerivingVia       #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# LANGUAGE GeneralizedNewtypeDeriving   #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE MultiParamTypeClasses   #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# OPTIONS_GHC -Wno-identities #-}
{-# OPTIONS_GHC -fno-omit-interface-pragmas #-}

module Week05.Seals(
      Seals (..)
    , getSeals
    , sealsSymbol
    , sealsToken
    -- * Constructors
    , fromValue
    , toValue
    , sealsOf
    , sealsValueOf
    -- * Num operations
    , divide
    -- * Etc.
    , isZero
    , policy
    ) where

import qualified Prelude                          as Haskell

import           Data.Fixed

import           Codec.Serialise.Class            (Serialise)
import           Data.Aeson                       (FromJSON, ToJSON)
import           Data.Tagged
import           Data.Text.Prettyprint.Doc.Extras
import           GHC.Generics                     (Generic)
import qualified Plutus.V1.Ledger.Value           as TH
import qualified PlutusTx                         as PlutusTx
import           PlutusTx.Lift                    (makeLift)
import           PlutusTx.Prelude                 hiding (divide)
import qualified Ledger.Typed.Scripts             as Scripts
import           Ledger                           (txId, PubKeyHash, Tx, TxOutTx (..), ValidatorHash, Address(..), ScriptContext, scriptAddress, mkMonetaryPolicyScript)
import qualified Ledger.Contexts                  as V
import qualified PlutusTx.Prelude                 as P
import qualified Data.ByteString.Char8 as C 

-- | Mint Policies 

mkPolicy :: ScriptContext -> Bool
mkPolicy _ = True

policy :: Scripts.MonetaryPolicy
policy = mkMonetaryPolicyScript $$(PlutusTx.compile [|| Scripts.wrapMonetaryPolicy mkPolicy ||])

{-# INLINABLE sealsSymbol #-}
-- | The 'CurrencySymbol' of the 'Seals' currency.
sealsSymbol :: TH.CurrencySymbol
sealsSymbol = V.scriptCurrencySymbol policy

{-# INLINABLE sealsToken #-}
-- | The 'TokenName' of the 'Seals' currency.
sealsToken :: TH.TokenName
sealsToken = TH.TokenName "Seal"

-- | The special currency on the Cardano blockchain. The unit of Seals is Seal
newtype Seals = Seals { getSeals :: Integer }
    deriving (Enum)
    deriving stock (Haskell.Eq, Haskell.Ord, Show, Generic)
    deriving anyclass (ToJSON, FromJSON)
    deriving newtype (Eq, Ord, Haskell.Num, AdditiveSemigroup, AdditiveMonoid, AdditiveGroup, MultiplicativeSemigroup, MultiplicativeMonoid, Integral, Real, Serialise, PlutusTx.IsData)

instance Haskell.Semigroup Seals where
    Seals a1 <> Seals a2 = Seals (a1 + a2)

instance Semigroup Seals where
    Seals a1 <> Seals a2 = Seals (a1 + a2)

instance Haskell.Monoid Seals where
    mempty = Seals 0

instance Monoid Seals where
    mempty = Seals 0

makeLift ''Seals

{-# INLINABLE toValue #-}
-- | Create a 'Value' containing only the given 'Seals'.
toValue :: Seals -> TH.Value
toValue (Seals i) = TH.singleton sealsSymbol sealsToken i

{-# INLINABLE fromValue #-}
-- | Get the 'Seals' in the given 'Value'.
fromValue :: TH.Value -> Seals
fromValue v = Seals (TH.valueOf v sealsSymbol sealsToken)

{-# INLINABLE sealsOf #-}
-- | Create 'Seals' representing the given quantity of Seals (the unit of the currency Seals).
sealsOf :: Integer -> Seals
sealsOf = Seals

{-# INLINABLE sealsValueOf #-}
-- | A 'Value' with the given amount of Seals (the currency unit).
--
--   @sealsValueOf == toValue . sealsOf@
--
sealsValueOf :: Integer -> TH.Value
sealsValueOf = TH.singleton sealsSymbol sealsToken

{-# INLINABLE divide #-}
-- | Divide one 'Seals' value by another.
divide :: Seals -> Seals -> Seals
divide (Seals a) (Seals b) = Seals (P.divide a b)

{-# INLINABLE isZero #-}
-- | Check whether an 'Seals' value is zero.
isZero :: Seals -> Bool
isZero (Seals i) = i == 0

Then I start my contract as follows:

mint :: Contract () Schema T.Text ()
mint = do
    MintParams name amount <- endpoint @"mint"
    let val     = Value.singleton Seals.sealsSymbol Seals.sealsToken amount
        datum   = MyDatum 100
        lookups =  Constraints.scriptInstanceLookups starterInstance <> Constraints.monetaryPolicy Seals.policy
        tx      = (Constraints.mustForgeValue val) <> (Constraints.mustPayToTheScript datum val)  
    ledgerTx <- submitTxConstraintsWith @Starter lookups tx

    void $ awaitTxConfirmed $ txId ledgerTx
    Contract.logInfo @String $ printf "forged Sym:%s %s" (show Seals.sealsSymbol) (show val)

The "Datum" and the name field in the mint parameters are not being used for anything special, the important part here is that this transaction will create "amount" tokens and pay them to the script.

So my objective is to:

  1. Keep the native tokens inside the contract, they can not be transferred to any other address

  2. The transaction must burn one of the tokens each time.

I am implementing with this some sort of counter, where the contract is started with X funds, then every transaction will decrease the currency by one and won't allow the funds to be transferred to any other address.

This is the validation code:

validateSpend :: MyDatum -> MyRedeemer -> ScriptContext -> Bool
validateSpend (MyDatum num) (MyRedeemer num2) ctx@ScriptContext{scriptContextTxInfo=txInfo@TxInfo{txInfoValidRange}} =
    let
        forged            = V.txInfoForge txInfo
        expected          = Seals.sealsValueOf (-1)
        newValueLocked    = getSeals $ Seals.fromValue (Validation.valueLockedBy txInfo (Validation.ownHash ctx))
        valueSpent        = getSeals $ Seals.fromValue (Validation.valueSpent txInfo)
        valPaidToScript   = getSeals $ Seals.fromValue (valuePaidToScript txInfo (Validation.ownHash ctx))
    in traceIfFalse "Value burned different than 1" (valPaidToScript == (valueSpent - 1)) && 
       traceIfFalse "Wrong number" (num == num2) &&
       traceIfFalse "Value forged different from expected" (expected == forged)

If I try to run this, I get the following error while compiling the contract:

GHC Core to PLC plugin: E042:Error: Unsupported feature: Type constructor: GHC.Prim.ByteArray#
Context: Compiling type: GHC.Prim.ByteArray#
Context: Compiling data constructor type: GHC.Integer.Type.BN#
Context: Compiling type: GHC.Integer.Type.BigNat
Context: Compiling data constructor type: GHC.Natural.NatJ#
Context: Compiling type: GHC.Natural.Natural
Context: Compiling data constructor type: PlutusCore.Core.Type.Version
Context: Compiling type: PlutusCore.Core.Type.Version ann
Context: Compiling data constructor type: UntypedPlutusCore.Core.Type.Program
Context: Compiling type: UntypedPlutusCore.Core.Type.Program
                           PlutusCore.DeBruijn.Internal.DeBruijn
                           PlutusCore.Universe.Default.DefaultUni
                           PlutusCore.Builtins.DefaultFun
                           ()
Context: Compiling type: Plutus.

If I replace the calls to my custom module for my native token with calls to the ADA module (and use the ADA currency) it works (without the burning token part). It seems that any code I execute inside the validator script that calls any of these functions:

{-# INLINABLE sealsSymbol #-}
-- | The 'CurrencySymbol' of the 'Seals' currency.
sealsSymbol :: TH.CurrencySymbol
sealsSymbol = V.scriptCurrencySymbol policy

{-# INLINABLE sealsToken #-}
-- | The 'TokenName' of the 'Seals' currency.
sealsToken :: TH.TokenName
sealsToken = TH.TokenName "Seal"

Causes the error. Both the token name and the currency symbol are stored as ByteStrings, so I am thinking that I can't compile hardcoded bytestrings on the contract validator script?

How can I use my custom currency module inside the validator script?

1
  • You can derive (Semigroup, Monoid) via Sum Integer using DerivingVia Commented Jun 14, 2021 at 17:25

1 Answer 1

10

The problem is in your sealsToken function, where you use a ByteString-literal. This is not possible if you want to compile to Plutus Script.

A trick to work around this issue is to add an additional argument to your validateSpend function of type TokenName, where you pass in the Seal token name as an additional argument.

I have used this trick in Lecture #7 of the Plutus Pioneer Program.

3
  • Yes, I saw it in the lecture and solved it that way. Thanks Commented May 24, 2021 at 7:11
  • 1
    @Lars Brünjes : Doesn't that also mean that the token name cannot be enforced by the on chain validator. Is there a way for validator to check for a particular AssetClass?
    – lambda
    Commented Mar 20, 2022 at 10:37
  • @lambda No it can still enforce a TokenName, because the TokenName is now being embedded in the script at compile time. So the validator just checks that any tokens being minted match the TokenName supplied when it was compiled.
    – zing
    Commented Apr 16, 2023 at 20:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.