diff --git a/.gitattributes b/.gitattributes index f520835a3..27142fa60 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ spago-packages.nix linguist-generated=true flake.lock linguist-generated=true server/config/pparams.json linguist-generated=true +fixtures/**/*.json linguist-generated=true diff --git a/CHANGELOG.md b/CHANGELOG.md index f525fe81f..851875814 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] -## [1.0.0] - 2022-06-07 +## [1.0.0] - 2022-06-10 CTL's initial release! diff --git a/packages.dhall b/packages.dhall index 4c931f90e..329c5cd03 100644 --- a/packages.dhall +++ b/packages.dhall @@ -152,6 +152,38 @@ let additions = , repo = "https://github.com/mlabs-haskell/purescript-aeson.git" , version = "69bd18c4a9cffdebc45c55d2448740721a91854c" } + , aeson-helpers = + { dependencies = + [ "aff" + , "argonaut-codecs" + , "argonaut-core" + , "arrays" + , "bifunctors" + , "contravariant" + , "control" + , "effect" + , "either" + , "enums" + , "foldable-traversable" + , "foreign-object" + , "maybe" + , "newtype" + , "ordered-collections" + , "prelude" + , "profunctor" + , "psci-support" + , "quickcheck" + , "record" + , "spec" + , "spec-quickcheck" + , "transformers" + , "tuples" + , "typelevel-prelude" + ] + , repo = + "https://github.com/mlabs-haskell/purescript-bridge-aeson-helpers.git" + , version = "44d0dae060cf78babd4534320192b58c16a6f45b" + } , sequences = { dependencies = [ "arrays" diff --git a/spago-packages.nix b/spago-packages.nix index 220a1f716..8559e6f09 100644 --- a/spago-packages.nix +++ b/spago-packages.nix @@ -17,6 +17,18 @@ let installPhase = "ln -s $src $out"; }; + "aeson-helpers" = pkgs.stdenv.mkDerivation { + name = "aeson-helpers"; + version = "44d0dae060cf78babd4534320192b58c16a6f45b"; + src = pkgs.fetchgit { + url = "https://github.com/mlabs-haskell/purescript-bridge-aeson-helpers.git"; + rev = "44d0dae060cf78babd4534320192b58c16a6f45b"; + sha256 = "1fgvaqvd9145zz5xw3fsa5vm75kp6bxcwa2nzq1dx2367h3a0zl0"; + }; + phases = "installPhase"; + installPhase = "ln -s $src $out"; + }; + "aff" = pkgs.stdenv.mkDerivation { name = "aff"; version = "v6.0.0"; @@ -1097,6 +1109,18 @@ let installPhase = "ln -s $src $out"; }; + "spec-quickcheck" = pkgs.stdenv.mkDerivation { + name = "spec-quickcheck"; + version = "v4.0.0"; + src = pkgs.fetchgit { + url = "https://github.com/purescript-spec/purescript-spec-quickcheck.git"; + rev = "c2991f475b8fa11de8b68bcb5895b36be04d1e82"; + sha256 = "01xcbfyqzax9c5najbfy12q0nvfklfm37llj2vkmi3wgkskg4prz"; + }; + phases = "installPhase"; + installPhase = "ln -s $src $out"; + }; + "st" = pkgs.stdenv.mkDerivation { name = "st"; version = "v5.0.1"; @@ -1133,6 +1157,18 @@ let installPhase = "ln -s $src $out"; }; + "text-encoding" = pkgs.stdenv.mkDerivation { + name = "text-encoding"; + version = "v1.0.0"; + src = pkgs.fetchgit { + url = "https://github.com/AlexaDeWit/purescript-text-encoding.git"; + rev = "609ea0916f6817971d4a6c11b991b59715aaa096"; + sha256 = "1r6ihj6m6ahp1cjf4i25pq9a00r2mvgrd8794xiapzsaigljz42c"; + }; + phases = "installPhase"; + installPhase = "ln -s $src $out"; + }; + "these" = pkgs.stdenv.mkDerivation { name = "these"; version = "v5.0.0"; diff --git a/spago.dhall b/spago.dhall index 5d3033510..338581387 100644 --- a/spago.dhall +++ b/spago.dhall @@ -5,6 +5,7 @@ You can edit this file as you like. { name = "cardano-transaction-lib" , dependencies = [ "aeson" + , "aeson-helpers" , "aff" , "aff-promise" , "affjax" @@ -54,6 +55,7 @@ You can edit this file as you like. , "spec" , "strings" , "tailrec" + , "text-encoding" , "these" , "transformers" , "tuples" diff --git a/src/Cardano/Types/Value.purs b/src/Cardano/Types/Value.purs index 9b214cda0..1bdcfccf6 100644 --- a/src/Cardano/Types/Value.purs +++ b/src/Cardano/Types/Value.purs @@ -50,9 +50,7 @@ import Prelude hiding (join) import Aeson ( class DecodeAeson , class EncodeAeson - , JsonDecodeError - ( TypeMismatch - ) + , JsonDecodeError(TypeMismatch) , caseAesonObject , encodeAeson' , getField diff --git a/src/Deserialization/FromBytes.purs b/src/Deserialization/FromBytes.purs index 575128238..091a1f88e 100644 --- a/src/Deserialization/FromBytes.purs +++ b/src/Deserialization/FromBytes.purs @@ -28,7 +28,7 @@ import Serialization.Types , TransactionWitnessSet , VRFKeyHash ) -import Type.Prelude (Proxy(..)) +import Type.Prelude (Proxy(Proxy)) import Type.Row (type (+)) import Types.ByteArray (ByteArray) diff --git a/src/Plutus/Types/Address.purs b/src/Plutus/Types/Address.purs index 3b87027e5..139684905 100644 --- a/src/Plutus/Types/Address.purs +++ b/src/Plutus/Types/Address.purs @@ -10,24 +10,41 @@ module Plutus.Types.Address import Prelude -import Data.Maybe (Maybe(Just, Nothing)) +import Aeson + ( class DecodeAeson + , class EncodeAeson + , JsonDecodeError(TypeMismatch) + , caseAesonObject + , encodeAeson' + , (.:) + ) +import Data.Either (Either(Left)) import Data.Generic.Rep (class Generic) -import Data.Show.Generic (genericShow) +import Data.Maybe (Maybe(Just, Nothing)) import Data.Newtype (class Newtype, wrap, unwrap) -import FromData (class FromData, fromData) +import Data.Show.Generic (genericShow) +import FromData (class FromData, genericFromData) +import Plutus.Types.Credential + ( Credential(PubKeyCredential, ScriptCredential) + , StakingCredential(StakingHash) + ) +import Plutus.Types.DataSchema + ( class HasPlutusSchema + , type (:+) + , type (:=) + , type (@@) + , I + , PNil + ) import Serialization.Address (NetworkId) -import ToData (class ToData, toData) -import Types.Scripts (ValidatorHash) -import Types.PlutusData (PlutusData(Constr)) +import ToData (class ToData, genericToData) +import TypeLevel.Nat (Z) import Types.PubKeyHash ( PaymentPubKeyHash(PaymentPubKeyHash) - , PubKeyHash , StakePubKeyHash + , PubKeyHash ) -import Plutus.Types.Credential - ( Credential(PubKeyCredential, ScriptCredential) - , StakingCredential(StakingHash) - ) +import Types.Scripts (ValidatorHash) -------------------------------------------------------------------------------- -- Address @@ -61,18 +78,34 @@ derive instance Generic Address _ instance Show Address where show = genericShow +instance + HasPlutusSchema + Address + ( "Address" + := + ( "addressCredential" := I Credential :+ "addressStakingCredential" + := I (Maybe StakingCredential) + :+ PNil + ) + @@ Z + :+ PNil + ) + instance ToData Address where - toData (Address a) = Constr zero $ - [ toData a.addressCredential, toData a.addressStakingCredential ] + toData = genericToData instance FromData Address where - fromData (Constr n [ credD, stakingCredD ]) | n == zero = - Address <$> - ( { addressCredential: _, addressStakingCredential: _ } - <$> fromData credD - <*> fromData stakingCredD - ) - fromData _ = Nothing + fromData = genericFromData + +instance DecodeAeson Address where + decodeAeson = caseAesonObject (Left $ TypeMismatch "Expected object") $ + \obj -> do + addressCredential <- obj .: "addressCredential" + addressStakingCredential <- obj .: "addressStakingCredential" + pure $ Address { addressCredential, addressStakingCredential } + +instance EncodeAeson Address where + encodeAeson' (Address addr) = encodeAeson' addr -------------------------------------------------------------------------------- -- Useful functions diff --git a/src/Plutus/Types/AssocMap.purs b/src/Plutus/Types/AssocMap.purs index e2cc2b3c0..442a9882b 100644 --- a/src/Plutus/Types/AssocMap.purs +++ b/src/Plutus/Types/AssocMap.purs @@ -20,21 +20,10 @@ module Plutus.Types.AssocMap import Prelude +import Aeson (class DecodeAeson, class EncodeAeson) import Data.Array ((:)) -import Data.Array - ( any - , deleteAt - , filter - , findIndex - , mapMaybe - , null - , singleton - ) as Array +import Data.Array (any, deleteAt, filter, findIndex, mapMaybe, null, singleton) as Array import Data.Bifunctor (bimap) -import Data.Generic.Rep (class Generic) -import Data.Maybe (Maybe(Just, Nothing), isJust) -import Data.Newtype (class Newtype, unwrap) -import Data.Foldable (lookup) as Foldable import Data.Foldable ( class Foldable , foldlDefault @@ -42,6 +31,10 @@ import Data.Foldable , foldr , foldrDefault ) +import Data.Foldable (lookup) as Foldable +import Data.Generic.Rep (class Generic) +import Data.Maybe (Maybe(Just, Nothing), isJust) +import Data.Newtype (class Newtype, unwrap) import Data.Show.Generic (genericShow) import Data.These (These(Both, That, This), these) import Data.Traversable (class Traversable, for, sequence, traverse) @@ -67,6 +60,8 @@ derive instance Generic (Map k v) _ derive instance Newtype (Map k v) _ derive newtype instance (Eq k, Eq v) => Eq (Map k v) derive newtype instance (Ord k, Ord v) => Ord (Map k v) +derive newtype instance (EncodeAeson k, EncodeAeson v) => EncodeAeson (Map k v) +derive newtype instance (DecodeAeson k, DecodeAeson v) => DecodeAeson (Map k v) instance (Show k, Show v) => Show (Map k v) where show = genericShow @@ -222,4 +217,4 @@ mapMaybeWithKey -> Map k a -> Map k b mapMaybeWithKey f (Map xs) = - Map $ Array.mapMaybe (\(k /\ v) -> (k /\ _) <$> f k v) xs \ No newline at end of file + Map $ Array.mapMaybe (\(k /\ v) -> (k /\ _) <$> f k v) xs diff --git a/src/Plutus/Types/Credential.purs b/src/Plutus/Types/Credential.purs index dc265434b..b198ca175 100644 --- a/src/Plutus/Types/Credential.purs +++ b/src/Plutus/Types/Credential.purs @@ -4,15 +4,31 @@ module Plutus.Types.Credential ) where import Prelude -import Data.Maybe (Maybe(Nothing)) + +import Aeson (class DecodeAeson, class EncodeAeson, encodeAeson') +import Aeson.Decode ((), ()) +import Aeson.Encode ((>/\<)) +import Control.Lazy (defer) import Data.Generic.Rep (class Generic) import Data.Show.Generic (genericShow) -import Types.Scripts (ValidatorHash) -import Types.PlutusData (PlutusData(Constr)) +import Data.Tuple.Nested ((/\)) +import FromData (class FromData, genericFromData) +import Plutus.Types.DataSchema + ( class HasPlutusSchema + , type (:+) + , type (:=) + , type (@@) + , I + , PNil + ) +import ToData (class ToData, genericToData) +import TypeLevel.Nat (S, Z) +import Aeson.Decode as Decode +import Aeson.Encode as Encode +import Data.Map as Map +import Serialization.Address (CertificateIndex, Slot, TransactionIndex) import Types.PubKeyHash (PubKeyHash) -import Serialization.Address (Pointer) -import ToData (class ToData, toData) -import FromData (class FromData, fromData) +import Types.Scripts (ValidatorHash) -------------------------------------------------------------------------------- -- Credential @@ -36,17 +52,41 @@ derive instance Generic Credential _ instance Show Credential where show = genericShow +instance + HasPlutusSchema + Credential + ( "PubKeyCredential" := PNil @@ Z + :+ "ScriptCredential" + := PNil + @@ (S Z) + :+ PNil + ) + +-- NOTE: mlabs-haskell/purescript-bridge generated and applied here +instance EncodeAeson Credential where + encodeAeson' = encodeAeson' <<< + ( defer $ const $ case _ of + PubKeyCredential a -> Encode.encodeTagged "PubKeyCredential" a + Encode.value + ScriptCredential a -> Encode.encodeTagged "ScriptCredential" a + Encode.value + ) + +instance DecodeAeson Credential where + decodeAeson = defer $ const $ Decode.decode + $ Decode.sumType "Credential" + $ Map.fromFoldable + [ "PubKeyCredential" /\ Decode.content + (PubKeyCredential <$> Decode.value) + , "ScriptCredential" /\ Decode.content + (ScriptCredential <$> Decode.value) + ] + instance ToData Credential where - toData (PubKeyCredential pubKeyHash) = - Constr zero [ toData pubKeyHash ] - toData (ScriptCredential validatorHash) = - Constr one [ toData validatorHash ] + toData = genericToData instance FromData Credential where - fromData (Constr n [ pd ]) - | n == zero = PubKeyCredential <$> fromData pd - | n == one = ScriptCredential <$> fromData pd - fromData _ = Nothing + fromData = genericFromData -------------------------------------------------------------------------------- -- StakingCredential @@ -57,7 +97,11 @@ instance FromData Credential where -- | Staking credential used to assign rewards. data StakingCredential = StakingHash Credential - | StakingPtr Pointer + | StakingPtr + { slot :: Slot + , txIx :: TransactionIndex + , certIx :: CertificateIndex + } derive instance Eq StakingCredential derive instance Ord StakingCredential @@ -66,20 +110,47 @@ derive instance Generic StakingCredential _ instance Show StakingCredential where show = genericShow +instance + HasPlutusSchema + StakingCredential + ( "StakingHash" := PNil @@ Z + :+ "StakingPtr" + := + ( "slot" := I Slot :+ "txIx" := I TransactionIndex :+ "certIx" + := I CertificateIndex + :+ PNil + ) + @@ (S Z) + :+ PNil + ) + instance ToData StakingCredential where - toData (StakingHash credential) = - Constr zero [ toData credential ] - toData (StakingPtr ptr) = - Constr one [ toData ptr.slot, toData ptr.txIx, toData ptr.certIx ] + toData = genericToData instance FromData StakingCredential where - fromData (Constr n [ pd ]) | n == zero = - StakingHash <$> fromData pd - fromData (Constr n [ slotD, txIxD, certIxD ]) | n == one = - StakingPtr <$> - ( { slot: _, txIx: _, certIx: _ } - <$> fromData slotD - <*> fromData txIxD - <*> fromData certIxD - ) - fromData _ = Nothing + fromData = genericFromData + +-- NOTE: mlabs-haskell/purescript-bridge generated and applied here +instance EncodeAeson StakingCredential where + encodeAeson' = encodeAeson' <<< defer + ( const $ case _ of + StakingHash a -> Encode.encodeTagged "StakingHash" a Encode.value + StakingPtr ptr -> Encode.encodeTagged "StakingPtr" + (ptr.slot /\ ptr.txIx /\ ptr.certIx) + (Encode.tuple (Encode.value >/\< Encode.value >/\< Encode.value)) + ) + +instance DecodeAeson StakingCredential where + decodeAeson = defer $ const $ Decode.decode + $ Decode.sumType "StakingCredential" + $ Map.fromFoldable + [ "StakingHash" /\ Decode.content (StakingHash <$> Decode.value) + , "StakingPtr" /\ Decode.content + ( Decode.tuple $ toStakingPtr Decode.value Decode.value + Decode.value + ) + ] + where + toStakingPtr + :: Slot -> TransactionIndex -> CertificateIndex -> StakingCredential + toStakingPtr slot txIx certIx = StakingPtr { slot, txIx, certIx } diff --git a/src/Plutus/Types/CurrencySymbol.purs b/src/Plutus/Types/CurrencySymbol.purs index 5e7753ea4..0aa234608 100644 --- a/src/Plutus/Types/CurrencySymbol.purs +++ b/src/Plutus/Types/CurrencySymbol.purs @@ -13,11 +13,11 @@ import Prelude import Aeson ( class DecodeAeson , class EncodeAeson + , JsonDecodeError(TypeMismatch) , caseAesonObject , decodeAeson , encodeAeson' , getField - , JsonDecodeError(TypeMismatch) ) import Data.Either (Either(Left)) import Data.Maybe (Maybe) diff --git a/src/Plutus/Types/Value.purs b/src/Plutus/Types/Value.purs index d87e1bd8a..fa92f7185 100644 --- a/src/Plutus/Types/Value.purs +++ b/src/Plutus/Types/Value.purs @@ -26,9 +26,20 @@ module Plutus.Types.Value import Prelude hiding (eq) +import Aeson + ( class DecodeAeson + , class EncodeAeson + , caseAesonObject + , decodeAeson + , encodeAeson + , encodeAeson' + , getField + , JsonDecodeError(TypeMismatch) + ) import Control.Apply (lift3) import Data.Array (concatMap, filter) import Data.BigInt (BigInt) +import Data.Either (Either(Left)) import Data.Foldable (all) import Data.Generic.Rep (class Generic) import Data.Lattice (class JoinSemilattice, class MeetSemilattice) @@ -37,9 +48,9 @@ import Data.Newtype (class Newtype) import Data.These (These(Both, That, This), these) import Data.Tuple (fst) import Data.Tuple.Nested (type (/\), (/\)) -import FromData (class FromData, fromData) +import FromData (class FromData) import Helpers (showWithParens) -import ToData (class ToData, toData) +import ToData (class ToData) import Types.ByteArray (ByteArray) import Types.TokenName (TokenName, adaToken, mkTokenName) import Plutus.Types.AssocMap (Map(Map)) as Plutus @@ -54,6 +65,18 @@ import Plutus.Types.CurrencySymbol (CurrencySymbol, mkCurrencySymbol, adaSymbol) newtype Value = Value (Plutus.Map CurrencySymbol (Plutus.Map TokenName BigInt)) +derive newtype instance ToData Value +derive newtype instance FromData Value + +instance DecodeAeson Value where + decodeAeson = caseAesonObject + (Left $ TypeMismatch "Expected object") + (flip getField "getValue" >=> decodeAeson >>> map Value) + +instance EncodeAeson Value where + encodeAeson' (Value mph) = encodeAeson' $ encodeAeson + { "getValue": encodeAeson mph } + -- https://playground.plutus.iohkdev.io/doc/haddock/plutus-ledger-api/html/src/Plutus.V1.Ledger.Value.html#eq instance Eq Value where eq = checkBinRel (==) @@ -113,16 +136,6 @@ valueToCoin v = Coin $ valueOf v adaSymbol adaToken isCoinZero :: Coin -> Boolean isCoinZero (Coin i) = i == zero --------------------------------------------------------------------------------- --- ToData / FromData --------------------------------------------------------------------------------- - -instance ToData Value where - toData (Value mp) = toData mp - -instance FromData Value where - fromData = map Value <<< fromData - -------------------------------------------------------------------------------- -- Public -------------------------------------------------------------------------------- diff --git a/src/QueryM.purs b/src/QueryM.purs index d4b2852b1..2b474f0c8 100644 --- a/src/QueryM.purs +++ b/src/QueryM.purs @@ -30,6 +30,7 @@ module QueryM , getWalletCollateral , liftQueryM , listeners + , postAeson , mkDatumCacheWebSocketAff , mkOgmiosRequest , mkOgmiosWebSocketAff @@ -39,6 +40,7 @@ module QueryM , ownStakePubKeyHash , runQueryM , signTransaction + , scriptToAeson , signTransactionBytes , submitTxOgmios , traceQueryConfig diff --git a/src/Serialization/Address.purs b/src/Serialization/Address.purs index 65af9ad93..2037bb0fc 100644 --- a/src/Serialization/Address.purs +++ b/src/Serialization/Address.purs @@ -110,9 +110,10 @@ derive instance Newtype Slot _ derive instance Generic Slot _ derive newtype instance Eq Slot derive newtype instance Ord Slot +derive newtype instance DecodeAeson Slot +derive newtype instance EncodeAeson Slot derive newtype instance FromData Slot derive newtype instance ToData Slot -derive newtype instance DecodeAeson Slot instance Show Slot where show = genericShow @@ -123,9 +124,6 @@ instance Semigroup Slot where instance Monoid Slot where mempty = Slot zero -instance EncodeAeson Slot where - encodeAeson' (Slot uint) = encodeAeson' (UInt.toNumber uint) - -- it is an integer in ogmios -- bytestring in plutus -- uint32 in csl @@ -147,6 +145,8 @@ derive instance Eq TransactionIndex derive instance Ord TransactionIndex derive instance Newtype TransactionIndex _ derive instance Generic TransactionIndex _ +derive newtype instance DecodeAeson TransactionIndex +derive newtype instance EncodeAeson TransactionIndex derive newtype instance ToData TransactionIndex derive newtype instance FromData TransactionIndex @@ -159,6 +159,8 @@ derive instance Eq CertificateIndex derive instance Ord CertificateIndex derive instance Newtype CertificateIndex _ derive instance Generic CertificateIndex _ +derive newtype instance DecodeAeson CertificateIndex +derive newtype instance EncodeAeson CertificateIndex derive newtype instance ToData CertificateIndex derive newtype instance FromData CertificateIndex diff --git a/src/Serialization/Hash.purs b/src/Serialization/Hash.purs index 2122bf00f..d0479b1dc 100644 --- a/src/Serialization/Hash.purs +++ b/src/Serialization/Hash.purs @@ -19,14 +19,12 @@ import Aeson ( class DecodeAeson , class EncodeAeson , JsonDecodeError(TypeMismatch) - , caseAesonObject , caseAesonString , encodeAeson' - , getField ) -import Data.Either (Either(Left), note) +import Data.Either (Either(Left, Right), note) import Data.Function (on) -import Data.Maybe (Maybe(Nothing)) +import Data.Maybe (Maybe(Nothing, Just), maybe) import Data.Newtype (unwrap, wrap) import FfiHelpers (MaybeFfiHelper, maybeFfiHelper) import FromData (class FromData) @@ -34,8 +32,8 @@ import Metadata.FromMetadata (class FromMetadata) import Metadata.ToMetadata (class ToMetadata, toMetadata) import ToData (class ToData, toData) import Types.Aliases (Bech32String) -import Types.RawBytes (RawBytes, rawBytesToHex, hexToRawBytes) import Types.PlutusData (PlutusData(Bytes)) +import Types.RawBytes (RawBytes, rawBytesToHex, hexToRawBytes) import Types.TransactionMetadata (TransactionMetadatum(Bytes)) as Metadata -- | PubKeyHash and StakeKeyHash refers to blake2b-224 hash digests of Ed25519 @@ -76,6 +74,9 @@ instance DecodeAeson Ed25519KeyHash where <=< note (TypeMismatch "Invalid ByteArray") <<< hexToRawBytes ) +instance EncodeAeson Ed25519KeyHash where + encodeAeson' = encodeAeson' <<< rawBytesToHex <<< ed25519KeyHashToBytes + foreign import _ed25519KeyHashFromBytesImpl :: MaybeFfiHelper -> RawBytes @@ -142,14 +143,12 @@ instance FromMetadata ScriptHash where -- Corresponds to Plutus' `Plutus.V1.Ledger.Api.Script` Aeson instances instance DecodeAeson ScriptHash where - decodeAeson = - caseAesonObject (Left (TypeMismatch "Expected object")) $ - note (TypeMismatch "Expected hex-encoded script hash") - <<< (scriptHashFromBytes <=< hexToRawBytes) - <=< flip getField "getScriptHash" + decodeAeson = do + maybe (Left $ TypeMismatch "Expected hex-encoded script hash") Right <<< + caseAesonString Nothing (Just <=< scriptHashFromBytes <=< hexToRawBytes) instance EncodeAeson ScriptHash where - encodeAeson' sh = encodeAeson' (scriptHashToBytes sh) + encodeAeson' sh = encodeAeson' $ scriptHashToBytes sh foreign import _scriptHashFromBytesImpl :: MaybeFfiHelper diff --git a/src/Types/ByteArray.js b/src/Types/ByteArray.js index fd30cd09d..cc4986a8f 100644 --- a/src/Types/ByteArray.js +++ b/src/Types/ByteArray.js @@ -33,6 +33,28 @@ exports.concat_ = xs => ys => { exports.byteArrayToHex = arr => Buffer.from(arr).toString('hex'); +/* adapted from https://github.com/WebReflection/uint8-to-utf16/blob/master/esm/index.js + * (someone who knows javascript should like import that or something) + */ +const {ceil} = Math; +const {fromCharCode} = String; + +exports.byteArrayToUTF16le = uint8array => { + let extra = 0; + const output = []; + const {length} = uint8array; + const len = ceil(length / 2); + for (let j = 0, i = 0; i < len; i++) + output.push( + fromCharCode( + (uint8array[j++] << 8) + + (j < length ? uint8array[j++] : extra++) + ) + ); + output.push(fromCharCode(extra)); + return output.join(''); +}; + exports.hexToByteArray_ = nothing => just => hex => { for (var bytes = [], c = 0; c < hex.length; c += 2) { const chunk = hex.substr(c, 2); @@ -53,6 +75,8 @@ exports.hexToByteArrayUnsafe = hex => { exports.byteArrayFromIntArrayUnsafe = ints => new Uint8Array(ints); +exports.byteArrayFromInt16ArrayUnsafe = ints => new Uint8Array(ints.buffer, ints.byteOffset, ints.byteLength); + exports.byteArrayFromIntArray_ = nothing => just => ints => { if (ints.every(i => i < 256 && i >= 0)) { return just(new Uint8Array(ints)); diff --git a/src/Types/ByteArray.purs b/src/Types/ByteArray.purs index 68076c28a..6cad97770 100644 --- a/src/Types/ByteArray.purs +++ b/src/Types/ByteArray.purs @@ -3,12 +3,14 @@ module Types.ByteArray ( ByteArray(..) , byteArrayFromIntArray , byteArrayFromIntArrayUnsafe + , byteArrayFromInt16ArrayUnsafe , byteArrayFromAscii , byteArrayToHex , byteArrayToIntArray , byteLength , hexToByteArray , hexToByteArrayUnsafe + , byteArrayToUTF16le ) where import Prelude @@ -121,3 +123,7 @@ byteArrayFromAscii str = do let charCode = toCharCode cp if charCode <= 255 && charCode >= 0 then pure charCode else Nothing + +foreign import byteArrayFromInt16ArrayUnsafe :: Array Int -> ByteArray + +foreign import byteArrayToUTF16le :: ByteArray -> String diff --git a/src/Types/Datum.purs b/src/Types/Datum.purs index 4a70117d4..f7b1c0818 100644 --- a/src/Types/Datum.purs +++ b/src/Types/Datum.purs @@ -6,14 +6,18 @@ module Types.Datum import Prelude -import Aeson (class DecodeAeson) +import Aeson (class DecodeAeson, class EncodeAeson, encodeAeson') +import Aeson.Encode ((>$<)) +import Control.Lazy (defer) import Data.Generic.Rep (class Generic) -import Data.Newtype (class Newtype) +import Data.Newtype (class Newtype, unwrap) import Data.Show.Generic (genericShow) import FromData (class FromData) import ToData (class ToData, toData) import Types.PlutusData (PlutusData) import Types.Transaction (DataHash(DataHash)) as X +import Aeson.Decode as Decode +import Aeson.Encode as Encode -- | Define data types mirroring Plutus `Datum`, like `Datum` itself and -- | `Redeemer` where the latter is not to be confused with the CSL-stype @@ -28,10 +32,16 @@ derive newtype instance Eq Datum derive newtype instance FromData Datum derive newtype instance Ord Datum derive newtype instance ToData Datum -derive newtype instance DecodeAeson Datum + +instance EncodeAeson Datum where + encodeAeson' = encodeAeson' <<< + defer (const $ Encode.encode $ unwrap >$< Encode.value) + +instance DecodeAeson Datum where + decodeAeson = defer $ const $ Decode.decode $ Datum <$> Decode.value instance Show Datum where show = genericShow unitDatum :: Datum -unitDatum = Datum (toData unit) +unitDatum = Datum $ toData unit diff --git a/src/Types/Interval.purs b/src/Types/Interval.purs index b87ec66d7..dce854e71 100644 --- a/src/Types/Interval.purs +++ b/src/Types/Interval.purs @@ -56,9 +56,7 @@ import Aeson ( class DecodeAeson , class EncodeAeson , Aeson - , JsonDecodeError - ( TypeMismatch - ) + , JsonDecodeError(TypeMismatch) , aesonNull , decodeAeson , encodeAeson @@ -66,6 +64,11 @@ import Aeson , getField , isNull ) +import Aeson.Decode ((), ()) +import Aeson.Decode as Decode +import Aeson.Encode ((>$<), (>/\<)) +import Aeson.Encode as Encode +import Control.Lazy (defer) import Control.Monad.Error.Class (throwError) import Control.Monad.Except.Trans (ExceptT(ExceptT), runExceptT) import Data.Array (find, head, index, length) @@ -82,14 +85,16 @@ import Data.Lattice , class JoinSemilattice , class MeetSemilattice ) +import Data.Map as Map import Data.Maybe (Maybe(Just, Nothing), fromJust, maybe) import Data.Newtype (class Newtype, unwrap, wrap) import Data.Show.Generic (genericShow) import Data.Tuple.Nested (type (/\), (/\)) -import Data.UInt as UInt +import Data.UInt (fromString) as UInt import Effect (Effect) import Effect.Class (liftEffect) import Foreign.Object (Object) +import FromData (class FromData, genericFromData) import Helpers ( bigIntToUInt , liftEither @@ -98,6 +103,7 @@ import Helpers , showWithParens , uIntToBigInt ) +import Partial.Unsafe (unsafePartial) import Plutus.Types.DataSchema ( class HasPlutusSchema , type (:+) @@ -113,11 +119,9 @@ import QueryM.Ogmios , SystemStart , aesonObject ) -import TypeLevel.Nat (S, Z) -import ToData (class ToData, genericToData) -import FromData (class FromData, genericFromData) -import Partial.Unsafe (unsafePartial) import Serialization.Address (Slot(Slot)) +import ToData (class ToData, genericToData) +import TypeLevel.Nat (S, Z) -------------------------------------------------------------------------------- -- Interval Type and related @@ -128,6 +132,7 @@ import Serialization.Address (Slot(Slot)) type Closure = Boolean -- | A set extended with a positive and negative infinity. +data Extended :: Type -> Type data Extended a = NegInf | Finite a | PosInf instance @@ -224,6 +229,11 @@ instance Show a => Show (UpperBound a) where newtype Interval :: Type -> Type newtype Interval a = Interval { from :: LowerBound a, to :: UpperBound a } +derive instance Generic (Interval a) _ +derive newtype instance Eq a => Eq (Interval a) +derive instance Functor Interval +derive instance Newtype (Interval a) _ + instance HasPlutusSchema (Interval a) ( "Interval" @@ -237,10 +247,6 @@ instance :+ PNil ) -derive instance Generic (Interval a) _ -derive newtype instance Eq a => Eq (Interval a) -derive instance Functor Interval - instance Show a => Show (Interval a) where show = genericShow @@ -260,7 +266,42 @@ instance ToData a => ToData (Interval a) where toData = genericToData instance FromData a => FromData (Interval a) where - fromData = genericFromData + fromData i = genericFromData i + +instance EncodeAeson a => EncodeAeson (Interval a) where + encodeAeson' (Interval i) = encodeAeson' $ HaskInterval + { ivFrom: i.from, ivTo: i.to } + +instance DecodeAeson a => DecodeAeson (Interval a) where + decodeAeson a = do + HaskInterval i <- decodeAeson a + pure $ Interval { from: i.ivFrom, to: i.ivTo } + +-------------------------------------------------------------------------------- +-- POSIXTIME Type and related +-------------------------------------------------------------------------------- +-- Taken from https://playground.plutus.iohkdev.io/doc/haddock/plutus-ledger-api/html/Plutus-V1-Ledger-Time.html#t:POSIXTimeRange +-- Plutus rev: cc72a56eafb02333c96f662581b57504f8f8992f via Plutus-apps (localhost): abe4785a4fc4a10ba0c4e6417f0ab9f1b4169b26 +newtype POSIXTime = POSIXTime BigInt + +derive instance Generic POSIXTime _ +derive instance Newtype POSIXTime _ +derive newtype instance Eq POSIXTime +derive newtype instance Ord POSIXTime +-- There isn't an Enum instance for BigInt so we derive Semiring instead which +-- has consequences on how isEmpty and overlaps are defined in +-- Types.POSIXTimeRange (Interval API). +derive newtype instance Semiring POSIXTime +derive newtype instance FromData POSIXTime +derive newtype instance ToData POSIXTime +derive newtype instance DecodeAeson POSIXTime +derive newtype instance EncodeAeson POSIXTime + +instance Show POSIXTime where + show (POSIXTime pt) = showWithParens "POSIXTime" pt + +-- | An `Interval` of `POSIXTime`s. +type POSIXTimeRange = Interval POSIXTime -------------------------------------------------------------------------------- -- Helpers @@ -398,32 +439,6 @@ before h (Interval { from: from' }) = lowerBound h < from' after :: forall (a :: Type). Ord a => a -> Interval a -> Boolean after h (Interval { to: to' }) = upperBound h > to' --------------------------------------------------------------------------------- --- POSIXTime Type and related --------------------------------------------------------------------------------- --- Taken from https://playground.plutus.iohkdev.io/doc/haddock/plutus-ledger-api/html/Plutus-V1-Ledger-Time.html#t:POSIXTimeRange --- Plutus rev: cc72a56eafb02333c96f662581b57504f8f8992f via Plutus-apps (localhost): abe4785a4fc4a10ba0c4e6417f0ab9f1b4169b26 -newtype POSIXTime = POSIXTime BigInt - -derive instance Generic POSIXTime _ -derive instance Newtype POSIXTime _ -derive newtype instance Eq POSIXTime -derive newtype instance Ord POSIXTime --- There isn't an Enum instance for BigInt so we derive Semiring instead which --- has consequences on how isEmpty and overlaps are defined in --- Types.POSIXTimeRange (Interval API). -derive newtype instance Semiring POSIXTime -derive newtype instance FromData POSIXTime -derive newtype instance ToData POSIXTime -derive newtype instance DecodeAeson POSIXTime -derive newtype instance EncodeAeson POSIXTime - -instance Show POSIXTime where - show (POSIXTime pt) = showWithParens "POSIXTime" pt - --- | An `Interval` of `POSIXTime`s. To be used in off-chain CTL contracts -type POSIXTimeRange = Interval POSIXTime - -- | A newtype wrapper over `POSIXTimeRange` to represent the on-chain version -- | of an off-chain `POSIXTimeRange`. In particular, there are a few steps -- | in conversion: @@ -997,6 +1012,80 @@ derive instance Eq ToOnChainPosixTimeRangeError instance Show ToOnChainPosixTimeRangeError where show = genericShow +-- TO DO: https://github.com/Plutonomicon/cardano-transaction-lib/issues/169 +-- -- | Get the current slot number +-- currentSlot :: SlotConfig -> Effect Slot + +-- NOTE: mlabs-haskell/purescript-bridge generated and applied here + +newtype HaskInterval a = HaskInterval + { ivFrom :: LowerBound a, ivTo :: UpperBound a } + +derive instance Generic (HaskInterval a) _ +derive newtype instance Eq a => Eq (HaskInterval a) +derive instance Functor HaskInterval +derive instance Newtype (HaskInterval a) _ + +instance (EncodeAeson a) => EncodeAeson (HaskInterval a) where + encodeAeson' = encodeAeson' <<< + defer + ( const $ Encode.encode $ unwrap >$< + ( Encode.record + { ivFrom: Encode.value :: _ (LowerBound a) + , ivTo: Encode.value :: _ (UpperBound a) + } + ) + ) + +instance (DecodeAeson a) => DecodeAeson (HaskInterval a) where + decodeAeson = defer $ const $ Decode.decode $ + HaskInterval <$> Decode.record "Interval" + { ivFrom: Decode.value :: _ (LowerBound a) + , ivTo: Decode.value :: _ (UpperBound a) + } + +instance (EncodeAeson a) => EncodeAeson (LowerBound a) where + encodeAeson' = encodeAeson' <<< + defer + ( const $ Encode.encode $ (case _ of LowerBound a b -> (a /\ b)) >$< + (Encode.tuple (Encode.value >/\< Encode.value)) + ) + +instance (DecodeAeson a) => DecodeAeson (LowerBound a) where + decodeAeson = defer $ const $ Decode.decode + $ Decode.tuple + $ LowerBound Decode.value Decode.value + +instance (EncodeAeson a) => EncodeAeson (UpperBound a) where + encodeAeson' = encodeAeson' <<< + defer + ( const $ Encode.encode $ (case _ of UpperBound a b -> (a /\ b)) >$< + (Encode.tuple (Encode.value >/\< Encode.value)) + ) + +instance (DecodeAeson a) => DecodeAeson (UpperBound a) where + decodeAeson = defer $ const $ Decode.decode + $ Decode.tuple + $ UpperBound Decode.value Decode.value + +instance (EncodeAeson a) => EncodeAeson (Extended a) where + encodeAeson' = encodeAeson' <<< + defer + ( const $ case _ of + NegInf -> encodeAeson { tag: "NegInf" } + Finite a -> Encode.encodeTagged "Finite" a Encode.value + PosInf -> encodeAeson { tag: "PosInf" } + ) + +instance (DecodeAeson a) => DecodeAeson (Extended a) where + decodeAeson = defer $ const $ Decode.decode + $ Decode.sumType "Extended" + $ Map.fromFoldable + [ "NegInf" /\ pure NegInf + , "Finite" /\ Decode.content (Finite <$> Decode.value) + , "PosInf" /\ pure PosInf + ] + toOnChainPosixTimeRangeErrorStr :: String toOnChainPosixTimeRangeErrorStr = "ToOnChainPosixTimeRangeError" diff --git a/src/Types/Natural.purs b/src/Types/Natural.purs index 1e9f9767d..f4a269073 100644 --- a/src/Types/Natural.purs +++ b/src/Types/Natural.purs @@ -12,11 +12,7 @@ module Types.Natural import Prelude -import Aeson - ( class DecodeAeson - , JsonDecodeError(TypeMismatch) - , caseAesonBigInt - ) +import Aeson (class DecodeAeson, JsonDecodeError(TypeMismatch), caseAesonBigInt) import Data.BigInt (BigInt) import Data.BigInt as BigInt import Data.Either (Either(Left), note) diff --git a/src/Types/PlutusData.purs b/src/Types/PlutusData.purs index 20fd645d0..6847c5bd9 100644 --- a/src/Types/PlutusData.purs +++ b/src/Types/PlutusData.purs @@ -5,8 +5,8 @@ module Types.PlutusData import Prelude import Aeson - ( class EncodeAeson - , class DecodeAeson + ( class DecodeAeson + , class EncodeAeson , JsonDecodeError(UnexpectedValue) , decodeAeson , encodeAeson diff --git a/src/Types/PubKeyHash.purs b/src/Types/PubKeyHash.purs index 42b41e0a4..dc562e1b0 100644 --- a/src/Types/PubKeyHash.purs +++ b/src/Types/PubKeyHash.purs @@ -15,18 +15,23 @@ import Prelude import Aeson ( class DecodeAeson + , class EncodeAeson , JsonDecodeError(TypeMismatch) , caseAesonObject , decodeAeson + , encodeAeson' , getField ) +import Aeson.Decode as Decode +import Aeson.Encode as Encode import Data.Either (Either(Left)) import Data.Generic.Rep (class Generic) -import Data.Newtype (class Newtype, unwrap) +import Data.Newtype (class Newtype, unwrap, wrap) import Data.Show.Generic (genericShow) import FromData (class FromData) import Metadata.FromMetadata (class FromMetadata) import Metadata.ToMetadata (class ToMetadata) +import Record (get) import Serialization.Address ( Address , EnterpriseAddress @@ -42,6 +47,7 @@ import Serialization.Address ) import Serialization.Hash (Ed25519KeyHash) import ToData (class ToData) +import Type.Proxy (Proxy(Proxy)) newtype PubKeyHash = PubKeyHash Ed25519KeyHash @@ -57,12 +63,18 @@ derive newtype instance ToMetadata PubKeyHash instance Show PubKeyHash where show = genericShow --- This is needed for `ApplyArgs`. Plutus has an `getPubKeyHash` field so don't --- newtype derive. +-- NOTE: mlabs-haskell/purescript-bridge generated and applied here +instance EncodeAeson PubKeyHash where + encodeAeson' x = encodeAeson' $ Encode.encode + (Encode.record { getPubKeyHash: Encode.value :: _ (Ed25519KeyHash) }) + { getPubKeyHash: unwrap x } + instance DecodeAeson PubKeyHash where - decodeAeson = caseAesonObject - (Left $ TypeMismatch "Expected object") - (flip getField "getPubKeyHash" >=> decodeAeson >>> map PubKeyHash) + decodeAeson = map (wrap <<< get (Proxy :: Proxy "getPubKeyHash")) <<< + Decode.decode + ( Decode.record "getPubKeyHash " + { getPubKeyHash: Decode.value :: _ (Ed25519KeyHash) } + ) ed25519EnterpriseAddress :: forall (n :: Type) @@ -116,11 +128,9 @@ instance Show PaymentPubKeyHash where -- This is needed for `ApplyArgs`. Plutus has an `unPaymentPubKeyHash` field so -- don't newtype derive. instance DecodeAeson PaymentPubKeyHash where - decodeAeson = caseAesonObject - (Left $ TypeMismatch "Expected object") - ( flip getField "unPaymentPubKeyHash" >=> - decodeAeson >>> map PaymentPubKeyHash - ) + decodeAeson = caseAesonObject (Left $ TypeMismatch "Expected object") + $ flip getField "unPaymentPubKeyHash" >=> decodeAeson >>> map + PaymentPubKeyHash newtype StakePubKeyHash = StakePubKeyHash PubKeyHash diff --git a/src/Types/Rational.purs b/src/Types/Rational.purs index 4afed972d..801b2f176 100644 --- a/src/Types/Rational.purs +++ b/src/Types/Rational.purs @@ -14,16 +14,16 @@ import Prelude import Aeson ( class DecodeAeson , class EncodeAeson - , JsonDecodeError(TypeMismatch, UnexpectedValue) - , decodeAeson + , JsonDecodeError(UnexpectedValue) + , caseAesonObject , encodeAeson' , toStringifiedNumbersJson + , (.:) ) import Data.BigInt (BigInt) -import Data.BigInt (fromInt, toString, fromString) as BigInt -import Data.Either (Either(Left), note) +import Data.BigInt (fromInt) as BigInt +import Data.Either (Either(Left)) import Data.Maybe (Maybe(Just, Nothing), maybe) -import Data.Newtype (class Newtype) import Data.Ratio ((%), numerator, denominator) as Ratio import Data.Ratio (Ratio) import FromData (class FromData) @@ -44,47 +44,28 @@ derive newtype instance Semiring Rational derive newtype instance Ring Rational derive newtype instance CommutativeRing Rational --- Representation of Rational in Aeson, used internally -type RationalRep = - { unRational :: - { numerator :: StringifiedBigInt - , denominator :: StringifiedBigInt - } +type RationalRep a = + { numerator :: a + , denominator :: a } instance EncodeAeson Rational where encodeAeson' r = encodeAeson' - ( { "unRational": - { "numerator": StringifiedBigInt (numerator r) - , "denominator": StringifiedBigInt (denominator r) - } - } :: RationalRep + ( { "numerator": numerator r + , "denominator": denominator r + } ) instance DecodeAeson Rational where - decodeAeson r = do - { unRational: - { numerator: (StringifiedBigInt n :: StringifiedBigInt) - , denominator: (StringifiedBigInt d :: StringifiedBigInt) - } - } :: RationalRep <- decodeAeson r - maybe (Left $ UnexpectedValue $ toStringifiedNumbersJson r) pure $ n % d - -newtype StringifiedBigInt = StringifiedBigInt BigInt - -derive instance Eq StringifiedBigInt -derive instance Newtype StringifiedBigInt _ - -instance EncodeAeson StringifiedBigInt where - encodeAeson' (StringifiedBigInt bi) = encodeAeson' $ BigInt.toString bi - -instance DecodeAeson StringifiedBigInt where - decodeAeson = - decodeAeson >=> - BigInt.fromString - >>> note (TypeMismatch "expected stringified integer number") - >>> - map StringifiedBigInt + decodeAeson aes = caseAesonObject + (Left <<< UnexpectedValue <<< toStringifiedNumbersJson $ aes) + ( \obj -> do + (n :: BigInt) <- obj .: "numerator" + d <- obj .: "denominator" + maybe (Left <<< UnexpectedValue <<< toStringifiedNumbersJson $ aes) pure + $ n % d + ) + aes instance EuclideanRing Rational where degree _ = one diff --git a/src/Types/Scripts.purs b/src/Types/Scripts.purs index e94bc0ff6..e696bc68d 100644 --- a/src/Types/Scripts.purs +++ b/src/Types/Scripts.purs @@ -48,12 +48,12 @@ instance Show PlutusScript where show = genericShow decodeAesonHelper - ∷ ∀ (a ∷ Type) (b :: Type) + :: ∀ (a :: Type) (b :: Type) . DecodeAeson a => String - → (a -> b) - → Aeson - → Either JsonDecodeError b + -> (a -> b) + -> Aeson + -> Either JsonDecodeError b decodeAesonHelper constrName constr = caseAesonObject (Left $ TypeMismatch "Expected object") (flip getField constrName >=> decodeAeson >>> map constr) @@ -147,13 +147,8 @@ derive newtype instance FromData ValidatorHash derive newtype instance ToData ValidatorHash derive newtype instance FromMetadata ValidatorHash derive newtype instance ToMetadata ValidatorHash - -instance DecodeAeson ValidatorHash where - decodeAeson = decodeAesonHelper "getValidatorHash" ValidatorHash - -instance EncodeAeson ValidatorHash where - encodeAeson' (ValidatorHash hash) = - encodeAeson' { "getValidatorHash": hash } +derive newtype instance EncodeAeson ValidatorHash +derive newtype instance DecodeAeson ValidatorHash instance Show ValidatorHash where show = genericShow diff --git a/src/Types/TokenName.purs b/src/Types/TokenName.purs index e5809eb1d..24f610a8a 100644 --- a/src/Types/TokenName.purs +++ b/src/Types/TokenName.purs @@ -13,29 +13,36 @@ import Prelude import Aeson ( class DecodeAeson , class EncodeAeson - , JsonDecodeError - ( TypeMismatch - ) + , JsonDecodeError(TypeMismatch) , caseAesonObject , encodeAeson' , getField ) import Data.BigInt (BigInt) import Data.Bitraversable (ltraverse) -import Data.Either (Either(Left), note) +import Data.Char (toCharCode) +import Data.Either (Either(Left, Right), note, either) import Data.Map (Map) import Data.Map (fromFoldable) as Map -import Data.Maybe (Maybe(Nothing)) -import Data.Newtype (wrap) +import Data.Maybe (Maybe(Nothing, Just)) +import Data.Newtype (wrap, unwrap) +import Data.String.CodePoints (drop, take) +import Data.String.CodeUnits (toCharArray) import Data.Traversable (class Traversable, traverse) +import Data.TextDecoding (decodeUtf8) import Data.Tuple.Nested (type (/\)) import FromData (class FromData) import Metadata.FromMetadata (class FromMetadata) import Metadata.ToMetadata (class ToMetadata) import Serialization.Types (AssetName) as CSL import ToData (class ToData) -import Types.ByteArray (ByteArray, byteLength, hexToByteArray) -import Types.CborBytes (CborBytes, cborBytesToHex) +import Types.ByteArray + ( ByteArray + , byteArrayFromIntArray + , byteArrayToHex + , byteLength + ) +import Types.CborBytes (CborBytes, cborBytesToByteArray) newtype TokenName = TokenName CborBytes @@ -46,17 +53,56 @@ derive newtype instance ToMetadata TokenName derive newtype instance Ord TokenName derive newtype instance ToData TokenName +asBase16 :: ByteArray -> String +asBase16 ba = "0x" <> byteArrayToHex ba + +fromTokenName :: forall r. (ByteArray -> r) -> (String -> r) -> TokenName -> r +fromTokenName arrayHandler stringHandler (TokenName cba) = either + (const $ arrayHandler $ cborBytesToByteArray cba) + stringHandler + (decodeUtf8 <<< unwrap <<< cborBytesToByteArray $ cba) + +-- | Corresponds to following Haskell instance: +-- | +-- | ``` +-- | toJSON = JSON.object . Haskell.pure . (,) "unTokenName" . JSON.toJSON . +-- | fromTokenName +-- | (\bs -> Text.cons '\NUL' (asBase16 bs)) + +-- | (\t -> case Text.take 1 t of "\NUL" -> Text.concat ["\NUL\NUL", t]; _ -> t) +-- | ``` instance DecodeAeson TokenName where - decodeAeson = caseAesonObject - (Left $ TypeMismatch "Expected object") - ( note (TypeMismatch "Invalid TokenName") <<< mkTokenName - <=< note (TypeMismatch "Invalid ByteArray") <<< hexToByteArray - <=< flip getField "unTokenName" - ) + decodeAeson = caseAesonObject (Left $ TypeMismatch "Expected object") $ + \aes -> do + tkstr <- getField aes "unTokenName" + case take 3 tkstr of + """\NUL0x""" -> case tkFromStr (drop 3 tkstr) of + Nothing -> Left $ TypeMismatch ("Invalid TokenName E1: " <> tkstr) + Just tk -> Right tk + + """\NUL\NUL\NUL""" -> + note (TypeMismatch $ "Invalid TokenName E2: " <> tkstr) + $ tkFromStr (drop 2 tkstr) + _ -> note (TypeMismatch $ "Invalid TokenName E3: " <> tkstr) + $ tkFromStr tkstr + where + tkFromStr :: String -> Maybe TokenName + tkFromStr = map (TokenName <<< wrap) <<< byteArrayFromIntArray + <<< map toCharCode + <<< toCharArray + +-- FIXME: what if the tokenname is actually \0\0\0? haskell will break this assuming it +-- comes from purescript side +-- also we will break assuming it comes from haskell +-- this issue has to be fixed on the haskell side instance EncodeAeson TokenName where - encodeAeson' (TokenName ba) = encodeAeson' - { "unTokenName": cborBytesToHex ba } + encodeAeson' = encodeAeson' <<< { "unTokenName": _ } <<< fromTokenName + (("""\NUL""" <> _) <<< asBase16) + ( \t -> case take 1 t of + """\NUL""" -> """\NUL\NUL""" <> t + _ -> t + ) instance Show TokenName where show (TokenName tn) = "(TokenName " <> show tn <> ")" @@ -71,8 +117,9 @@ adaToken = TokenName mempty -- | Create a `TokenName` from a `ByteArray` since TokenName data constructor is -- | not exported mkTokenName :: ByteArray -> Maybe TokenName -mkTokenName byteArr = - if byteLength byteArr <= 32 then pure $ TokenName (wrap byteArr) else Nothing +mkTokenName byteArr + | byteLength byteArr <= 32 = pure $ TokenName $ wrap byteArr + | otherwise = Nothing foreign import assetNameName :: CSL.AssetName -> ByteArray diff --git a/src/Types/TxConstraints.purs b/src/Types/TxConstraints.purs index 4a1c4ab4e..24678abb6 100644 --- a/src/Types/TxConstraints.purs +++ b/src/Types/TxConstraints.purs @@ -217,7 +217,7 @@ mustPayWithDatumToPubKeyAddress pkh skh datum = -- | `mustPayToTheScript` or `mustPayToOtherScript`, as we have no notion -- | of a "current" script. Thus, we have the single constraint -- | `mustPayToScript`, and all scripts must be explicitly provided to build --- | the transaction. +-- | the transaction. mustPayToScript :: forall (i :: Type) (o :: Type) . ValidatorHash diff --git a/test/Data.purs b/test/Data.purs index 60aac6b94..7cc5a1fef 100644 --- a/test/Data.purs +++ b/test/Data.purs @@ -3,12 +3,13 @@ module Test.Data (suite, tests, uniqueIndicesTests) where import Prelude -import Contract.PlutusData (PlutusData(Constr, Integer)) +import Aeson (decodeAeson, encodeAeson, JsonDecodeError(TypeMismatch)) import Control.Lazy (fix) import Data.BigInt (BigInt) import Data.BigInt as BigInt +import Data.Either (Either(Left, Right)) import Data.Generic.Rep as G -import Data.Maybe (Maybe(Just, Nothing), fromJust) +import Data.Maybe (maybe, Maybe(Just, Nothing), fromJust) import Data.Show.Generic (genericShow) import Data.Traversable (for_, traverse_) import Data.Tuple (Tuple, uncurry) @@ -19,6 +20,15 @@ import FromData (class FromData, fromData, genericFromData) import Helpers (showWithParens) import Mote (group, test) import Partial.Unsafe (unsafePartial) +import Plutus.Types.AssocMap (Map(Map)) +import Plutus.Types.DataSchema + ( class HasPlutusSchema + , type (:+) + , type (:=) + , type (@@) + , I + , PNil + ) import Serialization (toBytes) import Serialization.PlutusData as PDS import Test.QuickCheck.Arbitrary (class Arbitrary, arbitrary, genericArbitrary) @@ -26,24 +36,74 @@ import Test.QuickCheck.Gen (Gen) import Test.Spec.Assertions (shouldEqual) import TestM (TestPlanM) import ToData (class ToData, genericToData, toData) +import Types.PlutusData (PlutusData(Constr, Integer)) import Type.RowList (Cons, Nil) -import Types.ByteArray (hexToByteArrayUnsafe) import TypeLevel.Nat (Z, S) -import Untagged.Union (asOneOf) - -import Plutus.Types.DataSchema - ( class HasPlutusSchema - , type (:+) - , type (:=) - , type (@@) - , I - , PNil - ) import TypeLevel.RowList (class AllUniqueLabels) import TypeLevel.RowList.Unordered.Indexed (NilI, ConsI, class UniqueIndices) +import Types.ByteArray (hexToByteArrayUnsafe) +import Untagged.Union (asOneOf) + +plutusDataAesonRoundTrip + :: forall (a :: Type). ToData a => FromData a => a -> Either JsonDecodeError a +plutusDataAesonRoundTrip x = do + maybe (Left $ TypeMismatch "") pure <<< fromData =<< + (encodeAeson (toData x) # decodeAeson) suite :: TestPlanM Unit suite = do + group "PlutusData Aeson representation tests" $ do + group "Primitives" do + test "Unit" do + let + input = unit + plutusDataAesonRoundTrip input `shouldEqual` Right input + group "Boolean" do + let + inputs = [ true, false ] + for_ inputs \input -> do + test (show input) do + plutusDataAesonRoundTrip input `shouldEqual` Right input + group "Maybe" do + let + inputs = [ Just true, Just false, Nothing ] + for_ inputs \input -> do + test (show input) do + plutusDataAesonRoundTrip input `shouldEqual` Right input + group "BigInt" do + let + inputs = + [ BigInt.fromInt 0 + , BigInt.fromInt 10000 + , BigInt.fromInt (-10000) + ] + for_ inputs \input -> do + test (show input) do + plutusDataAesonRoundTrip input `shouldEqual` Right input + test "Array" do + let + input = [ Just true, Just false, Nothing ] + plutusDataAesonRoundTrip input `shouldEqual` Right input + test "Map" do + let + input = Map + [ BigInt.fromInt 13 /\ + [ Map + [ BigInt.fromInt 17 /\ false + ] + ] + ] + plutusDataAesonRoundTrip input `shouldEqual` Right input + group "Generic" do + -- TODO: Quickcheckify + test "CType: from . to == id" do + let + input = C4 + ( Map + [ BigInt.fromInt 13 /\ [ Map [ BigInt.fromInt 17 /\ false ] ] + ] + ) + plutusDataAesonRoundTrip input `shouldEqual` Right input group "PlutusData representation tests: ToData/FromData" $ do group "Primitives" do test "Unit" do @@ -72,6 +132,20 @@ suite = do for_ inputs \input -> do test (show input) do fromData (toData input) `shouldEqual` Just input + test "Array" do + let + input = [ Just true, Just false, Nothing ] + fromData (toData input) `shouldEqual` Just input + test "Map" do + let + input = Map + [ BigInt.fromInt 13 /\ + [ Map + [ BigInt.fromInt 17 /\ false + ] + ] + ] + fromData (toData input) `shouldEqual` Just input group "Generic" do -- TODO: Quickcheckify test "EType: from . to == id" do @@ -181,6 +255,7 @@ data CType | C1 (Maybe MyBigInt) | C2 MyBigInt Boolean | C3 MyBigInt Boolean Boolean + | C4 (Map BigInt (Array (Map BigInt Boolean))) instance HasPlutusSchema CType @@ -194,6 +269,9 @@ instance :+ "C3" := PNil @@ (S (S (S Z))) + :+ "C4" + := PNil + @@ (S (S (S (S Z)))) :+ PNil ) @@ -456,7 +534,7 @@ testBinaryFixture value binaryFixture = do (hexToByteArrayUnsafe binaryFixture) -- | Poor man's type level tests -tests ∷ Array String +tests :: Array String tests = [ testNil , testSingleton @@ -479,7 +557,7 @@ tests = => String testUniques = "[A, B, C] is all unique and should compile" -uniqueIndicesTests ∷ Array String +uniqueIndicesTests :: Array String uniqueIndicesTests = [ testNil , testSingletonZ diff --git a/test/Deserialization.purs b/test/Deserialization.purs index 7e001108c..957ed0222 100644 --- a/test/Deserialization.purs +++ b/test/Deserialization.purs @@ -194,10 +194,10 @@ suite = do group "WitnessSet - deserialization is inverse to serialization" do let witnessSetRoundTrip - ∷ ∀ (m ∷ Type -> Type) + :: ∀ (m :: Type -> Type) . MonadEffect m => MonadThrow Error m - ⇒ ByteArray + => ByteArray -> m Unit witnessSetRoundTrip fixture = do ws0 <- errMaybe "Failed deserialization" $ diff --git a/test/Fixtures.purs b/test/Fixtures.purs index 83d517510..1b7b5cce5 100644 --- a/test/Fixtures.purs +++ b/test/Fixtures.purs @@ -1088,7 +1088,7 @@ plutusDataFixture7 = PD.List , plutusDataFixture6 ] -plutusDataFixture8 ∷ PD.PlutusData +plutusDataFixture8 :: PD.PlutusData plutusDataFixture8 = PD.Constr (BigInt.fromInt 0) [ PD.Bytes ( hexToByteArrayUnsafe @@ -1107,14 +1107,14 @@ plutusDataFixture8 = PD.Constr (BigInt.fromInt 0) , PD.Integer (BigInt.fromInt 45) ] -plutusDataFixture8Bytes ∷ ByteArray +plutusDataFixture8Bytes :: ByteArray plutusDataFixture8Bytes = hexToByteArrayUnsafe "d8799f581cda13ed22b9294f1d86bbd530e99b1456884c7364bf16c90edc1ae41e1a1dcd6500\ \581c82325cbfc20b85bd1ca12e5d12b44b83f68662d8395167b45f1ff7fa4d746f6e6573206f\ \6620736b7920581cda13ed22b9294f1d86bbd530e99b1456884c7364bf16c90edc1ae41e\ \182dff" -plutusDataFixture8Bytes' ∷ ByteArray +plutusDataFixture8Bytes' :: ByteArray plutusDataFixture8Bytes' = hexToByteArrayUnsafe "d866820086581cda13ed22b9294f1d86bbd530e99b1456884c7364bf16c90edc1ae41e1a1dcd\ \6500581c82325cbfc20b85bd1ca12e5d12b44b83f68662d8395167b45f1ff7fa4d746f6e6573\ diff --git a/test/OgmiosDatumCache.purs b/test/OgmiosDatumCache.purs index 10ec7b6d1..47bcd637a 100644 --- a/test/OgmiosDatumCache.purs +++ b/test/OgmiosDatumCache.purs @@ -37,7 +37,7 @@ readPlutusDataSamples = do "./fixtures/test/ogmios-datum-cache/plutus-data-samples.json" plutusDataToFromAesonTest - ∷ forall (m ∷ Type -> Type). MonadEffect m => MonadThrow Error m => m Unit + :: forall (m :: Type -> Type). MonadEffect m => MonadThrow Error m => m Unit plutusDataToFromAesonTest = do pdsAes <- readAeson "./fixtures/test/ogmios-datum-cache/plutus-data-samples.json" @@ -50,7 +50,7 @@ plutusDataToFromAesonTest = do aes `shouldEqual` aes' plutusDataHashingTest - ∷ forall (m ∷ Type -> Type). MonadEffect m => MonadThrow Error m => m Unit + :: forall (m :: Type -> Type). MonadEffect m => MonadThrow Error m => m Unit plutusDataHashingTest = do plutusDataSamples <- readPlutusDataSamples let elems = plutusDataSamples diff --git a/test/Plutus/Address.purs b/test/Plutus/Address.purs index cf4855954..4cc9e0593 100644 --- a/test/Plutus/Address.purs +++ b/test/Plutus/Address.purs @@ -1,13 +1,13 @@ -module Test.Plutus.Address (suite) where +module Test.Plutus.Address (suite, addresses) where import Prelude -import Data.Maybe (Maybe(Just, Nothing), fromJust) -import Data.Tuple (Tuple(Tuple)) import Data.Array ((..), length, zip) +import Data.Maybe (Maybe(Just, Nothing), fromJust) import Data.Newtype (class Newtype, wrap, unwrap) -import Data.UInt (UInt, fromInt) import Data.Traversable (for_) +import Data.Tuple (Tuple(Tuple)) +import Data.UInt (UInt, fromInt) import Data.Tuple.Nested ((/\)) import Mote (group, test) import Partial.Unsafe (unsafePartial) @@ -18,13 +18,13 @@ import Plutus.Types.Credential ( Credential(PubKeyCredential, ScriptCredential) , StakingCredential(StakingHash, StakingPtr) ) -import Serialization.Hash (ed25519KeyHashFromBech32, scriptHashFromBech32) import Serialization.Address ( NetworkId(MainnetId, TestnetId) , addressFromBech32 ) +import Serialization.Hash (ed25519KeyHashFromBech32, scriptHashFromBech32) import Test.Spec.Assertions (shouldEqual) -import Test.Utils (errMaybe) +import Test.Utils (errMaybe, toFromAesonTest) import TestM (TestPlanM) import Types.Aliases (Bech32String) @@ -41,6 +41,10 @@ suite = do let testData = zip (zip addressesBech32Testnet addresses) indices for_ testData $ \(Tuple (Tuple addrBech32 addr) addrType) -> toFromPlutusTypeTest TestnetId addrType addrBech32 addr + group "Aeson tests" $ do + group "Roundtrip tests" + $ for_ addresses + $ toFromAesonTest "Address" toFromPlutusTypeTest :: NetworkId -> Int -> Bech32String -> Plutus.Address -> TestPlanM Unit diff --git a/test/Plutus/Credential.purs b/test/Plutus/Credential.purs new file mode 100644 index 000000000..366ab6279 --- /dev/null +++ b/test/Plutus/Credential.purs @@ -0,0 +1,47 @@ +module Test.Plutus.Credential + ( suite + , creds + ) where + +import Prelude + +import Data.Maybe (fromJust) +import Data.Newtype (wrap) +import Data.Traversable (for_) +import Mote (group) +import Partial.Unsafe (unsafePartial) +import Plutus.Types.Credential (Credential(ScriptCredential, PubKeyCredential)) +import Serialization.Hash (ed25519KeyHashFromBech32, scriptHashFromBech32) +import Test.Utils (toFromAesonTest) +import TestM (TestPlanM) +import Types.Aliases (Bech32String) + +suite :: TestPlanM Unit +suite = do + group "Plutus.Types.Credential" $ do + group "Aeson tests" $ do + group "Roundtrip tests" $ for_ creds $ toFromAesonTest "Credential" + +creds :: Array Credential +creds = + [ pubKeyCredential + , scriptCredential + ] + +paymentKeyBech32 :: Bech32String +paymentKeyBech32 = + "addr_vkh1jjfnzhxe966a33psfenm0ct2udkkr569qf55v4uprgkgu8zsvmg" + +pubKeyCredential :: Credential +pubKeyCredential = + PubKeyCredential <<< wrap <<< unsafePartial fromJust $ + ed25519KeyHashFromBech32 paymentKeyBech32 + +scriptBech32 :: Bech32String +scriptBech32 = + "script1cda3khwqv60360rp5m7akt50m6ttapacs8rqhn5w342z7r35m37" + +scriptCredential :: Credential +scriptCredential = + ScriptCredential <<< wrap <<< unsafePartial fromJust $ + scriptHashFromBech32 scriptBech32 diff --git a/test/Unit.purs b/test/Unit.purs index 23820e17c..b521b6ab7 100644 --- a/test/Unit.purs +++ b/test/Unit.purs @@ -12,6 +12,7 @@ import Test.Metadata.Seabug as Seabug import Test.Metadata.Cip25 as Cip25 import Test.Parser as Parser import Test.Plutus.Address as Plutus.Address +import Test.Plutus.Credential as Plutus.Credential import Test.Plutus.Time as Plutus.Time import Test.Plutus.Value as Plutus.Value import Test.Serialization as Serialization @@ -37,6 +38,7 @@ testPlan = do Hashing.suite Parser.suite Plutus.Address.suite + Plutus.Credential.suite Plutus.Time.suite Plutus.Value.suite Seabug.suite