209 lines
6.1 KiB
Haskell
209 lines
6.1 KiB
Haskell
{- |
|
|
Module : Test.Util
|
|
Maintainer : emi@haskell.fyi
|
|
Description: Utility functions for testing Plutarch scripts with ScriptContext
|
|
-}
|
|
module Test.Util (
|
|
-- * Plutus-land utils
|
|
datumHash,
|
|
toDatum,
|
|
toDatumHash,
|
|
datumPair,
|
|
closedBoundedInterval,
|
|
updateMap,
|
|
sortMap,
|
|
sortValue,
|
|
blake2b_224,
|
|
pubKeyHashes,
|
|
userCredentials,
|
|
scriptCredentials,
|
|
validatorHashes,
|
|
groupsOfN,
|
|
mkSpending,
|
|
mkMinting,
|
|
CombinableBuilder,
|
|
) where
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
import Codec.Serialise (serialise)
|
|
import Crypto.Hash qualified as Crypto
|
|
import Data.Bifunctor (second)
|
|
import Data.ByteArray qualified as BA
|
|
import Data.ByteString qualified as BS
|
|
import Data.ByteString.Char8 qualified as C
|
|
import Data.ByteString.Lazy qualified as ByteString.Lazy
|
|
import Data.List (sortOn)
|
|
import Plutarch.Context (
|
|
Builder,
|
|
buildMintingUnsafe,
|
|
buildSpendingUnsafe,
|
|
withMinting,
|
|
withSpendingOutRef,
|
|
)
|
|
import Plutarch.Crypto (pblake2b_256)
|
|
import PlutusLedgerApi.V1 (
|
|
Credential (
|
|
PubKeyCredential,
|
|
ScriptCredential
|
|
),
|
|
CurrencySymbol,
|
|
PubKeyHash (..),
|
|
ScriptContext,
|
|
TxOutRef,
|
|
ValidatorHash (ValidatorHash),
|
|
)
|
|
import PlutusLedgerApi.V1.Interval qualified as PlutusTx
|
|
import PlutusLedgerApi.V1.Scripts (Datum (Datum), DatumHash (DatumHash))
|
|
import PlutusLedgerApi.V1.Value (Value (..))
|
|
import PlutusTx.AssocMap qualified as AssocMap
|
|
import PlutusTx.Builtins qualified as PlutusTx
|
|
import PlutusTx.IsData qualified as PlutusTx
|
|
import PlutusTx.Ord qualified as PlutusTx
|
|
import Prelude
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
{- | Create a pair from data for use in 'PlutusLedgerApi.V1.Contexts.txInfoData'.
|
|
|
|
Example:
|
|
@
|
|
myTxInfo { 'PlutusLedgerApi.V1.Contexts.txInfoData' = ['datumPair' myDatum] }
|
|
@
|
|
-}
|
|
datumPair :: PlutusTx.ToData a => a -> (DatumHash, Datum)
|
|
datumPair = (,) <$> toDatumHash <*> toDatum
|
|
|
|
-- | Calculate the blake2b-256 hash of a Datum.
|
|
datumHash :: Datum -> DatumHash
|
|
datumHash (Datum data') = toDatumHash data'
|
|
|
|
-- | Convenience function to create a Datum from any type that implements ToData.
|
|
toDatum :: PlutusTx.ToData a => a -> Datum
|
|
toDatum = Datum . PlutusTx.toBuiltinData
|
|
|
|
{- | Calculate the blake2b-256 hash of any type that implements ToData
|
|
|
|
Shamelessly go through plutus.
|
|
-}
|
|
toDatumHash :: PlutusTx.ToData a => a -> DatumHash
|
|
toDatumHash datum =
|
|
DatumHash $
|
|
PlutusTx.toBuiltin $
|
|
plift $
|
|
pblake2b_256
|
|
# pconstant (ByteString.Lazy.toStrict $ serialise $ PlutusTx.toData datum)
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Create a closed bounded `Interval`.
|
|
closedBoundedInterval :: PlutusTx.Ord a => a -> a -> PlutusTx.Interval a
|
|
closedBoundedInterval from to = PlutusTx.intersection (PlutusTx.from from) (PlutusTx.to to)
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
{- | / O(n) /. The expression @'updateMap' f k v@ will update the value @x@ at key @k@.
|
|
If @f x@ is Nothing, the key-value pair will be deleted from the map, otherwise the
|
|
value will be updated.
|
|
-}
|
|
updateMap :: Eq k => (v -> Maybe v) -> k -> AssocMap.Map k v -> AssocMap.Map k v
|
|
updateMap f k =
|
|
AssocMap.mapMaybeWithKey
|
|
( \k' v ->
|
|
if k' == k
|
|
then f v
|
|
else Just v
|
|
)
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Sort the given 'AssocMap.Map' by keys in ascending order.
|
|
sortMap :: forall k v. Ord k => AssocMap.Map k v -> AssocMap.Map k v
|
|
sortMap =
|
|
AssocMap.fromList
|
|
. sortOn fst
|
|
. AssocMap.toList
|
|
|
|
{- | Sort the given 'Value' in ascending order. Some plutarch functions that
|
|
work with plutarch's 'Sorted' 'PMap' require this to work correctly.
|
|
-}
|
|
sortValue :: Value -> Value
|
|
sortValue =
|
|
Value
|
|
. sortMap
|
|
. AssocMap.fromList
|
|
. fmap (second sortMap)
|
|
. AssocMap.toList
|
|
. getValue
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Compute the hash of a given byte string using blake2b_224 algorithm.
|
|
blake2b_224 :: BS.ByteString -> BS.ByteString
|
|
blake2b_224 = BS.pack . BA.unpack . Crypto.hashWith Crypto.Blake2b_224
|
|
|
|
-- | An infinite list of blake2b_224 hashes.
|
|
blake2b_224Hashes :: [BS.ByteString]
|
|
blake2b_224Hashes = blake2b_224 . C.pack . show @Integer <$> [0 ..]
|
|
|
|
-- | An infinite list of *valid* 'PubKeyHash'.
|
|
pubKeyHashes :: [PubKeyHash]
|
|
pubKeyHashes = PubKeyHash . PlutusTx.toBuiltin <$> blake2b_224Hashes
|
|
|
|
-- | An infinite list of *valid* user credentials.
|
|
userCredentials :: [Credential]
|
|
userCredentials = PubKeyCredential <$> pubKeyHashes
|
|
|
|
-- | An infinite list of *valid* validator hashes.
|
|
validatorHashes :: [ValidatorHash]
|
|
validatorHashes = ValidatorHash . PlutusTx.toBuiltin <$> blake2b_224Hashes
|
|
|
|
-- | An infinite list of *valid* script credentials.
|
|
scriptCredentials :: [Credential]
|
|
scriptCredentials = ScriptCredential <$> validatorHashes
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Turn the given list in to groups which have the given length.
|
|
groupsOfN :: Int -> [a] -> [[a]]
|
|
groupsOfN _ [] = []
|
|
groupsOfN n xs =
|
|
let (nextGroup, rest) = next n xs
|
|
in nextGroup : groupsOfN n rest
|
|
where
|
|
next :: Int -> [a] -> ([a], [a])
|
|
next _ [] = ([], [])
|
|
next 0 xs = ([], xs)
|
|
next n (x : xs) =
|
|
let (xs', rest) = next (n - 1) xs
|
|
in (x : xs', rest)
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
{- | Given the builder generator and the parameters, create a 'ScriptContext'
|
|
that spends the UTXO that referenced by the given 'TxOutRef'.
|
|
-}
|
|
mkSpending ::
|
|
forall ps.
|
|
(forall b. (Monoid b, Builder b) => ps -> b) ->
|
|
ps ->
|
|
TxOutRef ->
|
|
ScriptContext
|
|
mkSpending mkBuilder ps oref =
|
|
buildSpendingUnsafe $
|
|
mkBuilder ps <> withSpendingOutRef oref
|
|
|
|
{- | Given the builder generator and the parameters, create a 'ScriptContext'
|
|
that mints the token of the given currency symbol.
|
|
-}
|
|
mkMinting ::
|
|
forall ps.
|
|
(forall b. (Monoid b, Builder b) => ps -> b) ->
|
|
ps ->
|
|
CurrencySymbol ->
|
|
ScriptContext
|
|
mkMinting mkBuilder ps cs =
|
|
buildMintingUnsafe $
|
|
mkBuilder ps <> withMinting cs
|
|
|
|
type CombinableBuilder b = (Monoid b, Builder b)
|