I tried to create a few modification on the NFT minting contract from week 5 of the plutus pioneer program. I wanted to be able to mint a few tokens at once. My policy should require that the token names start with "ABC" + Integer where the integer is less than 100.
Here is my attempt:
{-# INLINABLE mkPolicy #-}
mkPolicy :: TxOutRef -> () -> ScriptContext -> Bool
mkPolicy oref () ctx = traceIfFalse "UTxO not consumed" hasUTxO &&
traceIfFalse "wrong amount minted" checkMintedAmount
where
info :: TxInfo
info = scriptContextTxInfo ctx
hasUTxO :: Bool
hasUTxO = any (\i -> txInInfoOutRef i == oref) $ txInfoInputs info
flattenVal :: [(CurrencySymbol, TokenName, Integer)]
flattenVal = flattenValue (txInfoMint info)
-- Check that TokenName is of the form ABC1 .. ABC99
checkTokenName :: TokenName -> Bool
checkTokenName tn = isPrefixOf "ABC" (show tn) &&
isJust (readMaybe (post tn) :: Maybe Integer) &&
(read (post tn) :: Integer) < 100
where
post tn' = drop 3 $ show tn'
checkMintedAmount :: Bool
checkMintedAmount = all (\(_, tn', amt') -> checkTokenName tn' && amt' == 1) flattenVal
Unfortunately this gives a horrible run time error which would be too long to paste here, so I'll just paste the first few lines:
GHC Core to PLC plugin: E042:Error: Unsupported feature: Int: use Integer instead
Context: Compiling type: GHC.Types.Int
Context: Compiling type: GHC.Types.Int -> a -> GHC.Show.ShowS
Context: Compiling data constructor type: GHC.Show.C:Show
Context: Compiling type: GHC.Show.Show a
Context: Compiling expr: \ ($dShow_a14Ch [Occ=Once*] :: GHC.Show.Show a)
(tn' [Occ=Once*] :: a) ->
let {
n :: GHC.Types.Int
[LclId,
Unf=Unf{Src=<vanilla>, TopLvl=False, Value=True, ConLike=True,
WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 20}]
n = GHC.Types.I# 3# } in
letrec {
unsafeDrop [Occ=LoopBreaker]
:: forall a1. GHC.Types.Int -> [a1] -> [a1]
[LclId,
Arity=2,
Unf=Unf{Src=<vanilla>, TopLvl=False, Value=True, ConLike=True,
WorkFree=True, Expandable=True, Guidance=IF_ARGS [20 30] 91 10}]
unsafeDrop
= \ (@ a1)
(ds [Occ=Once!] :: GHC.Types.Int)
(ds1 [Occ=Once!] :: [a1]) ->
case ds of { GHC.Types.I# ipv ->
case ds1 of {
[] -> GHC.Types.[] @ a1;
: _ [Occ=Dead] ipv2 [Occ=Once*] ->
case ipv of {
__DEFAULT ->
unsafeDrop @ a1 (GHC.Types.I# (GHC.Prim.-# ipv 1#)) ipv2;
1# -> ipv2
}
}
}; } in
case GHC.Classes.leInt n (GHC.Types.I# 0#) of {
GHC.Types.False ->
unsafeDrop @ GHC.Types.Char n (GHC.Show.show @ a $dShow_a14Ch tn');
GHC.Types.True -> GHC.Show.show @ a $dShow_a14Ch tn'
}
I saw similar discussion on discord about trying to compare the token name to an empty string. Is this somehow related? I though the above would work because I'm explicitly doing show on the BuiltinByteString
Any idea what I'm doing wrong?
Update
Someone pointed out that the compile is failing due to my onchain code not containing inlinable functions. I think the prefix check should be doable using takeByteString and dropByteString. I'm still trying to figure out how would I check the integer part of the name? I can't find anything equivalent to ReadMaybe.
Update 2
I'm inching closer to a solution based on the feedback in the comments and in the Q&A that happened on 15 Feb Here is the updated attempt to use only inlinable functions
-- Check that TokenName is of the form ABC1 .. ABC99
checkTokenName :: TokenName -> Bool
checkTokenName tn = lengthOfByteString bs > 3
&& checkInt tokNum
where
bs = unTokenName tn
tokNum = sliceByteString 3 2 bs
tokNumLen = lengthOfByteString tokNum
checkInt bs' = all (\i -> Builtins.greaterThanInteger i 47 && Builtins.lessThanInteger i 58)
-- Seems this list comprehension causes issues
[indexByteString bs' i | i <- [0..tokNumLen]]
Update 3
I tried the advise of Sjoerd Visscher, but unfortunately I get the same type of error if I use any function with recursion or a pattern matching. For example
checkTokenName :: TokenName -> Bool
checkTokenName tn = lengthOfByteString bs > 3
&& checkInt tokNum tokNumLen
where
bs = unTokenName tn
tokNum = sliceByteString 3 2 bs
tokNumLen = lengthOfByteString tokNum
checkInt _ 0 = True
checkInt bs' i = True && checkInt bs' (i-1)
Results in:
GHC Core to PLC plugin: E042:Error: Unsupported feature: Use of Haskell Integer equality, possibly via the Haskell Eq typeclass
Context: Compiling expr: GHC.Integer.Type.eqInteger#
Context: Compiling expr: GHC.Integer.Type.eqInteger# ds_d12x1
Context: Compiling expr: GHC.Integer.Type.eqInteger# ds_d12x1 0
Context: Compiling expr: case GHC.Integer.Type.eqInteger# ds_d12x1 0
of wild [Occ=Once]
{ __DEFAULT ->
GHC.Prim.tagToEnum# @ GHC.Types.Bool wild
}
...
It seems that only a function with no pattern matching or recursion works here.
checkTokenName :: TokenName -> Bool
checkTokenName tn = lengthOfByteString bs > 3
&& lengthOfByteString bs < 6
&& checkInt tokNum tokNumLen
where
bs = unTokenName tn
tokNum = sliceByteString 3 2 bs
tokNumLen = lengthOfByteString tokNum
isNum i = Builtins.greaterThanInteger i 47 && Builtins.lessThanInteger i 58
checkInt :: BuiltinByteString -> Integer -> Bool
-- No recursion or pattern matching !?!?
checkInt bs' len = (len == 1 && isNum (indexByteString bs' 0)) ||
(len == 2 && isNum (indexByteString bs' 0) &&
isNum (indexByteString bs' 1))