3

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))

3
  • 1
    Lars gave you the answer in the Q&A if you couldn't attend. Please, check here: zoom.us/rec/share/…
    – kindofdev
    Commented Feb 15, 2022 at 20:56
  • Is Update 2 working? If not, could you paste the error you get? Commented Feb 16, 2022 at 9:34
  • @SjoerdVisscher Sorry, I didn't make it very obvious. Update 2 is not working. I added a comment above the list comprehension that causes an issue. I believe it's the same error as before about not using inlinable types. I'll try update the question when I'm back at my home computer.
    – Avatar33
    Commented Feb 16, 2022 at 12:16

1 Answer 1

1

You can't use the [0..tokNumLen] syntax, that translates to a function from the Enum class from base which is not inlinable.

You could try something like this:

-- Check that TokenName is of the form ABC1 .. ABC99
    checkTokenName :: TokenName ->  Bool
    checkTokenName tn = lengthOfByteString bs > 3
                        && 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 bs' 0 = True
            checkInt bs' i = isNum (indexByteString bs' (i - 1)) && checkInt bs' (i - 1)
1
  • Unfortunately it seems any attempt to use recursion results in the same error.
    – Avatar33
    Commented Feb 19, 2022 at 10:56

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.