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:
Keep the native tokens inside the contract, they can not be transferred to any other address
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?
(Semigroup, Monoid) via Sum Integer
usingDerivingVia