diff --git a/CHANGELOG.md b/CHANGELOG.md index c1e1b98..d8e5444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +# 0.2.0 - UNRELEASED + # 0.1.2 - **Critical fix** for `PlyArg Credential` instance - The instance was buggy and did not produce proper UPLC. Any types containing `Credential` (e.g `Address`) may have been affected. diff --git a/ply-core/ply-core.cabal b/ply-core/ply-core.cabal index c342135..e5d1f64 100644 --- a/ply-core/ply-core.cabal +++ b/ply-core/ply-core.cabal @@ -1,6 +1,6 @@ cabal-version: 3.0 name: ply-core -version: 0.1.2 +version: 0.2.0 author: Chase license: MIT @@ -127,6 +127,10 @@ test-suite ply-core-test import: common-ghc-opts type: exitcode-stdio-1.0 main-is: Spec.hs - build-depends: ply-core + build-depends: + , ply-core + , tasty + , tasty-quickcheck + hs-source-dirs: test ghc-options: -threaded -rtsopts -with-rtsopts=-N diff --git a/ply-core/src/Ply/Core/Class.hs b/ply-core/src/Ply/Core/Class.hs index a559db4..072c004 100644 --- a/ply-core/src/Ply/Core/Class.hs +++ b/ply-core/src/Ply/Core/Class.hs @@ -1,16 +1,21 @@ +{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableSuperClasses #-} {-# OPTIONS_GHC -Wno-orphans #-} -module Ply.Core.Class (PlyArg (UPLCRep, ToDataConstraint, toBuiltinArg, toBuiltinArgData), someBuiltinArg) where +module Ply.Core.Class ( + PlyArg (UPLCRep, toBuiltinArg), + PlyArgData (toBuiltinArgData), + someBuiltinArg, + someBuiltinArgData, +) where import Data.Bifunctor (Bifunctor (bimap), second) import Data.ByteString (ByteString) import qualified Data.ByteString as BS -import Data.Kind (Constraint, Type) +import Data.Kind (Type) import qualified Data.Map.Strict as M import Data.Text (Text) -import GHC.TypeLits (ErrorMessage (ShowType, Text)) import PlutusCore (DefaultUni, Includes, Some, ValueOf) import qualified PlutusCore as PLC @@ -19,8 +24,6 @@ import qualified PlutusTx.AssocMap as PlutusMap import Ply.LedgerExports.Common import qualified Ply.LedgerExports.V1 as LedgerV1 -type BottomConstraint s t = 'Text s ~ ShowType t - {- | Class of haskell types that can be applied as arguments to a Plutus script. This class is meant to not only convert a type to its UPLC builtin representation, but _also verify_ any @@ -30,89 +33,110 @@ As a result, 'toBuiltinArg' is partial for types that don't hold their invariant == Laws - 1. If 'toBuiltinArg'/'toBuiltinArgData' is applied over `x`, all contained 'PlyArg's within should also have - their corresponding 'toBuiltinArg'/'toBuiltinArgData' applied over them. - - 2. If @UPLCRep a = Data@; @toBuiltinArgData = toBuiltinArg@ + 1. If 'toBuiltinArg' is applied over `x`, all contained 'PlyArg's within should also have + their corresponding 'toBuiltinArg' applied over them. - 3. Assuming a lawful 'Eq' instance (i.e the 'Eq' instance is aware of any invariants): - @fromData (toBuiltinArgData x) == Just x@ + 2. Assuming a lawful 'Eq' instance (i.e the 'Eq' instance is aware of any invariants): + @fromBuiltin' (toBuiltinArg x) == x@ - 4. 'toBuiltinArgData' implementation must perform the same validations/normalizations as 'toBuiltinArg'. + Where `fromBuiltin'` is capable of parsing the _correct_ UPLC representation of a type into the type itself. -} class DefaultUni `Includes` UPLCRep a => PlyArg a where type UPLCRep a :: Type - type ToDataConstraint a :: Constraint - type ToDataConstraint a = () toBuiltinArg :: a -> UPLCRep a - toBuiltinArgData :: ToDataConstraint a => a -> Data + +{- | Class of haskell types that can be applied as data encoded arguments to a Plutus script. + +Similar to 'PlyArg', this class should also verify the same invariants for each type. + +If 'UPLCRep a ~ Data', this instance is free and does not need any explicit method definition. + +== Laws + + 1. If @UPLCRep a = Data@; @toBuiltinArgData = toBuiltinArg@ + + Otherwise: @toBuiltinArgData = toData . toBuiltinArg@ + + (assume 'toData @ByteString' simply delegates to 'toData @BuiltinByteString') + + 2. Assuming a lawful 'Eq' instance (i.e the 'Eq' instance is aware of any invariants): + @fromData (toBuiltinArgData x) == Just x@ +-} +class PlyArg a => PlyArgData a where + toBuiltinArgData :: a -> Data + default toBuiltinArgData :: UPLCRep a ~ Data => a -> Data + toBuiltinArgData = toBuiltinArg -- | Create a PlutusCore 'Some' value from a 'PlyArg'. someBuiltinArg :: PlyArg a => a -> Some (ValueOf DefaultUni) someBuiltinArg = PLC.someValue . toBuiltinArg +-- | Create a PlutusCore 'Some' data value from a 'PlyArg'. +someBuiltinArgData :: PlyArgData a => a -> Some (ValueOf DefaultUni) +someBuiltinArgData = PLC.someValue . toBuiltinArgData + instance PlyArg Bool where type UPLCRep Bool = Bool toBuiltinArg = id +instance PlyArgData Bool where toBuiltinArgData = toData instance PlyArg Integer where type UPLCRep Integer = Integer toBuiltinArg = id +instance PlyArgData Integer where toBuiltinArgData = toData instance PlyArg () where type UPLCRep () = () toBuiltinArg = id +instance PlyArgData () where toBuiltinArgData = toData instance PlyArg BuiltinByteString where type UPLCRep BuiltinByteString = ByteString toBuiltinArg = fromBuiltin +instance PlyArgData BuiltinByteString where toBuiltinArgData = toData instance PlyArg ByteString where type UPLCRep ByteString = ByteString toBuiltinArg = id +instance PlyArgData ByteString where toBuiltinArgData = toData . toBuiltin --- | 'toBuiltinArgData' is uncallable for this instance. instance PlyArg Text where type UPLCRep Text = Text - type ToDataConstraint Text = BottomConstraint "toBuiltinArgData(Text): unsupported" Text toBuiltinArg = id - toBuiltinArgData = error "toBuiltinArgData(Text): uncallable function was called" instance PlyArg Data where type UPLCRep Data = Data toBuiltinArg = id - toBuiltinArgData = id +instance PlyArgData Data instance (PlyArg a, PlyArg b) => PlyArg (a, b) where type UPLCRep (a, b) = (UPLCRep a, UPLCRep b) - type ToDataConstraint (a, b) = (ToDataConstraint a, ToDataConstraint b) toBuiltinArg (x, y) = (toBuiltinArg x, toBuiltinArg y) +instance (PlyArgData a, PlyArgData b) => PlyArgData (a, b) where toBuiltinArgData (x, y) = Constr 0 [toBuiltinArgData x, toBuiltinArgData y] instance PlyArg a => PlyArg [a] where type UPLCRep [a] = [UPLCRep a] - type ToDataConstraint [a] = ToDataConstraint a toBuiltinArg = map toBuiltinArg +instance PlyArgData a => PlyArgData [a] where toBuiltinArgData l = List $ map toBuiltinArgData l -instance (PlyArg a, ToDataConstraint a) => PlyArg (Maybe a) where +instance PlyArgData a => PlyArg (Maybe a) where type UPLCRep (Maybe a) = Data toBuiltinArg Nothing = Constr 1 [] toBuiltinArg (Just x) = Constr 0 [toBuiltinArgData x] - toBuiltinArgData = toBuiltinArg +instance PlyArgData a => PlyArgData (Maybe a) -- | This instance sorts the map. instance - ( PlyArg k - , ToDataConstraint k + ( PlyArgData k , Ord k - , PlyArg v - , ToDataConstraint v + , PlyArgData v ) => PlyArg (PlutusMap.Map k v) where @@ -122,6 +146,14 @@ instance . M.toAscList . M.fromList . PlutusMap.toList + +instance + ( PlyArgData k + , Ord k + , PlyArgData v + ) => + PlyArgData (PlutusMap.Map k v) + where toBuiltinArgData = Map . toBuiltinArg -- | This instance sorts the 'Value' and removes all zero entries. @@ -144,6 +176,8 @@ instance PlyArg Value where . M.fromListWith (<>) . map (second (M.fromListWith (+) . PlutusMap.toList)) $ PlutusMap.toList m + +instance PlyArgData Value where toBuiltinArgData = Map . toBuiltinArg -- | This verifies the underlying bytestring is exactly 28 bytes. @@ -152,13 +186,14 @@ instance PlyArg Credential where toBuiltinArg cred = case cred of PubKeyCredential h -> Constr 0 [toBuiltinArgData h] ScriptCredential h -> Constr 1 [toBuiltinArgData h] - toBuiltinArgData = toBuiltinArg + +instance PlyArgData Credential instance PlyArg StakingCredential where type UPLCRep StakingCredential = Data toBuiltinArg (StakingHash cred) = Constr 0 [toBuiltinArg cred] toBuiltinArg x = toData x - toBuiltinArgData = toBuiltinArg +instance PlyArgData StakingCredential instance PlyArg Address where type UPLCRep Address = Data @@ -166,7 +201,7 @@ instance PlyArg Address where let credData = toBuiltinArg cred stakeCredData = toBuiltinArg stakeCred in Constr 0 [credData, stakeCredData] - toBuiltinArgData = toBuiltinArg +instance PlyArgData Address -- | This verifies the underlying bytestring is exactly 0 (for adaSymbol) or 28 bytes. instance PlyArg CurrencySymbol where @@ -179,6 +214,8 @@ instance PlyArg CurrencySymbol where else error "toBuiltinArg(CurrencySymbol): Expected exactly 0 or 28 bytes" where bs = fromBuiltin x + +instance PlyArgData CurrencySymbol where toBuiltinArgData = toBuiltinArgData . toBuiltinArg -- | This verifies the underlying bytestring is no longer than 32 bytes. @@ -190,12 +227,14 @@ instance PlyArg TokenName where else error "toBuiltinArg(TokenName): Expected 32 bytes or less" where bs = fromBuiltin x + +instance PlyArgData TokenName where toBuiltinArgData = toBuiltinArgData . toBuiltinArg instance PlyArg AssetClass where type UPLCRep AssetClass = Data toBuiltinArg (AssetClass i) = toBuiltinArgData i - toBuiltinArgData = toBuiltinArg +instance PlyArgData AssetClass -- | This verifies the underlying bytestring is exactly 28 bytes. instance PlyArg PubKeyHash where @@ -206,6 +245,8 @@ instance PlyArg PubKeyHash where else error "toBuiltinArg(PubKeyHash): Expected 28 bytes" where bs = fromBuiltin x + +instance PlyArgData PubKeyHash where toBuiltinArgData = toBuiltinArgData . toBuiltinArg -- | This verifies the underlying integer is non-negative. @@ -215,6 +256,8 @@ instance PlyArg POSIXTime where if i >= 0 then i else error "toBuiltinArg(POSIXTime): Expected non-negative" + +instance PlyArgData POSIXTime where toBuiltinArgData = toBuiltinArgData . toBuiltinArg -- | This verifies the underlying integer is non-negative. @@ -224,29 +267,28 @@ instance PlyArg DiffMilliSeconds where if i >= 0 then i else error "toBuiltinArg(DiffMilliSeconds): Expected non-negative" - toBuiltinArgData = toBuiltinArgData . toBuiltinArg -instance (PlyArg a, ToDataConstraint a) => PlyArg (Extended a) where +instance PlyArgData a => PlyArg (Extended a) where type UPLCRep (Extended a) = Data toBuiltinArg NegInf = Constr 0 [] toBuiltinArg (Finite a) = Constr 0 [toBuiltinArgData a] toBuiltinArg PosInf = Constr 1 [] - toBuiltinArgData = toBuiltinArg +instance PlyArgData a => PlyArgData (Extended a) -instance (PlyArg a, ToDataConstraint a) => PlyArg (UpperBound a) where +instance PlyArgData a => PlyArg (UpperBound a) where type UPLCRep (UpperBound a) = Data toBuiltinArg (UpperBound ext cls) = Constr 0 [toBuiltinArgData ext, toBuiltinArgData cls] - toBuiltinArgData = toBuiltinArg +instance PlyArgData a => PlyArgData (UpperBound a) -instance (PlyArg a, ToDataConstraint a) => PlyArg (LowerBound a) where +instance PlyArgData a => PlyArg (LowerBound a) where type UPLCRep (LowerBound a) = Data toBuiltinArg (LowerBound ext cls) = Constr 0 [toBuiltinArgData ext, toBuiltinArgData cls] - toBuiltinArgData = toBuiltinArg +instance PlyArgData a => PlyArgData (LowerBound a) -instance (PlyArg a, ToDataConstraint a) => PlyArg (Interval a) where +instance PlyArgData a => PlyArg (Interval a) where type UPLCRep (Interval a) = Data toBuiltinArg (Interval lb ub) = Constr 0 [toBuiltinArgData lb, toBuiltinArgData ub] - toBuiltinArgData = toBuiltinArg +instance PlyArgData a => PlyArgData (Interval a) instance PlyArg DCert where type UPLCRep DCert = Data @@ -257,7 +299,7 @@ instance PlyArg DCert where toBuiltinArg (DCertPoolRetire pkh i) = Constr 4 [toBuiltinArgData pkh, toBuiltinArgData i] toBuiltinArg DCertGenesis = Constr 5 [] toBuiltinArg DCertMir = Constr 6 [] - toBuiltinArgData = toBuiltinArg +instance PlyArgData DCert -- | This verifies the underlying bytestring is exactly 32 bytes. instance PlyArg TxId where @@ -269,7 +311,8 @@ instance PlyArg TxId where else error "toBuiltinArg(TxId): Expected 32 bytes" where bs = fromBuiltin x - toBuiltinArgData = toBuiltinArgData . toBuiltinArg + +instance PlyArgData TxId -- | This verifies the txIdx is non-negative. instance PlyArg TxOutRef where @@ -278,7 +321,8 @@ instance PlyArg TxOutRef where if txIdx >= 0 then Constr 0 [toBuiltinArgData txId, toBuiltinArgData txIdx] else error "toBuiltinArg(TxOutRef): Expected non-negative idx" - toBuiltinArgData = toBuiltinArg + +instance PlyArgData TxOutRef instance PlyArg LedgerV1.TxOut where type UPLCRep LedgerV1.TxOut = Data @@ -289,12 +333,12 @@ instance PlyArg LedgerV1.TxOut where , toBuiltinArgData val , toBuiltinArgData dh ] - toBuiltinArgData = toBuiltinArg +instance PlyArgData LedgerV1.TxOut instance PlyArg LedgerV1.TxInInfo where type UPLCRep LedgerV1.TxInInfo = Data toBuiltinArg (LedgerV1.TxInInfo ref out) = Constr 0 [toBuiltinArgData ref, toBuiltinArgData out] - toBuiltinArgData = toBuiltinArg +instance PlyArgData LedgerV1.TxInInfo instance PlyArg LedgerV1.TxInfo where type UPLCRep LedgerV1.TxInfo = Data @@ -324,7 +368,7 @@ instance PlyArg LedgerV1.TxInfo where , toBuiltinArgData datumMap , toBuiltinArgData txId ] - toBuiltinArgData = toBuiltinArg +instance PlyArgData LedgerV1.TxInfo instance PlyArg ScriptPurpose where type UPLCRep ScriptPurpose = Data @@ -332,22 +376,22 @@ instance PlyArg ScriptPurpose where toBuiltinArg (Spending ref) = Constr 1 [toBuiltinArgData ref] toBuiltinArg (Rewarding stakeCred) = Constr 2 [toBuiltinArgData stakeCred] toBuiltinArg (Certifying dcert) = Constr 3 [toBuiltinArgData dcert] - toBuiltinArgData = toBuiltinArg +instance PlyArgData ScriptPurpose instance PlyArg LedgerV1.ScriptContext where type UPLCRep LedgerV1.ScriptContext = Data toBuiltinArg (LedgerV1.ScriptContext inf purp) = Constr 0 [toBuiltinArgData inf, toBuiltinArgData purp] - toBuiltinArgData = toBuiltinArg +instance PlyArgData LedgerV1.ScriptContext instance PlyArg Datum where type UPLCRep Datum = Data toBuiltinArg (Datum d) = toData d - toBuiltinArgData = toBuiltinArg +instance PlyArgData Datum instance PlyArg Redeemer where type UPLCRep Redeemer = Data toBuiltinArg (Redeemer d) = toData d - toBuiltinArgData = toBuiltinArg +instance PlyArgData Redeemer -- | This verifies the underlying bytestring is exactly 28 bytes. instance PlyArg ScriptHash where @@ -358,6 +402,8 @@ instance PlyArg ScriptHash where else error "toBuiltinArg(ScriptHash): Expected 28 bytes" where bs = fromBuiltin x + +instance PlyArgData ScriptHash where toBuiltinArgData = toBuiltinArgData . toBuiltinArg -- | This verifies the underlying bytestring is exactly 28 bytes. @@ -369,6 +415,8 @@ instance PlyArg ValidatorHash where else error "toBuiltinArg(ValidatorHash): Expected 28 bytes" where bs = fromBuiltin x + +instance PlyArgData ValidatorHash where toBuiltinArgData = toBuiltinArgData . toBuiltinArg -- | This verifies the underlying bytestring is exactly 28 bytes. @@ -380,6 +428,8 @@ instance PlyArg MintingPolicyHash where else error "toBuiltinArg(MintingPolicyHash): Expected 28 bytes" where bs = fromBuiltin x + +instance PlyArgData MintingPolicyHash where toBuiltinArgData = toBuiltinArgData . toBuiltinArg -- | This verifies the underlying bytestring is exactly 28 bytes. @@ -391,6 +441,8 @@ instance PlyArg DatumHash where else error "toBuiltinArg(DatumHash): Expected 28 bytes" where bs = fromBuiltin x + +instance PlyArgData DatumHash where toBuiltinArgData = toBuiltinArgData . toBuiltinArg -- | This verifies the underlying bytestring is exactly 28 bytes. @@ -402,4 +454,6 @@ instance PlyArg RedeemerHash where else error "toBuiltinArg(RedeemerHash): Expected 28 bytes" where bs = fromBuiltin x + +instance PlyArgData RedeemerHash where toBuiltinArgData = toBuiltinArgData . toBuiltinArg diff --git a/ply-plutarch/ply-plutarch.cabal b/ply-plutarch/ply-plutarch.cabal index 19267f3..b5b9c15 100644 --- a/ply-plutarch/ply-plutarch.cabal +++ b/ply-plutarch/ply-plutarch.cabal @@ -1,6 +1,6 @@ cabal-version: 3.0 name: ply-plutarch -version: 0.1.2 +version: 0.2.0 author: Chase license: MIT diff --git a/ply-plutarch/src/Ply/Plutarch/Class.hs b/ply-plutarch/src/Ply/Plutarch/Class.hs index 967cd5b..68125cf 100644 --- a/ply-plutarch/src/Ply/Plutarch/Class.hs +++ b/ply-plutarch/src/Ply/Plutarch/Class.hs @@ -12,7 +12,14 @@ import qualified PlutusTx.AssocMap as PlutusMap -- TODO: How to handle 'PAsData'? --- | 'PlyArgOf' yields the corresponding Haskell type for a given Plutarch type. +{- | 'PlyArgOf' yields the corresponding Haskell type for a given Plutarch type. + +== Laws + +'PlyArgOf p' should be the _proper Haskell synonym_ of the Plutarch type p. Refer to +the Plutarch docs, specifically 'PIsData' and 'PConstant'/'PLift', for information +on type representation and correspondence. +-} type PlyArgOf :: PType -> Type type family PlyArgOf a = r | r -> a