Skip to main content
5 of 5
added 879 characters in body
Avatar33
  • 201
  • 1
  • 5

Working with BuiltinByteString

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

Avatar33
  • 201
  • 1
  • 5