Skip to content

Commit ca7aa7b

Browse files
committed
Add ToJSON/FromJSON instances for EraScript
* Add ToJSON/FromJSON as superclass constraints to EraScript * Add ToJSON/FromJSON instances for MultiSig, Timelock and DijkstraNativeScript * Add structured JSON serialisation for native scripts across all eras (Shelley, Allegra, Dijkstra) * Add ToJSON/FromJSON instances for AlonzoScript with structured JSON format * Rename kindObject (returning Value) to kindObjectValue; add new kindObject returning Aeson.Object * Fix shelleyBasedEraNativeScriptToJSON and sizedNativeScriptGens to accept a child continuation to correctly handle nested scripts across eras * Add round-trip JSON property tests for NativeScript and Script to the shared era spec
1 parent ebed62d commit ca7aa7b

26 files changed

Lines changed: 402 additions & 94 deletions

File tree

eras/allegra/impl/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* Add `ApplyTick` instance for `AllegraEra`
88
* Add `EraForecast` and `ShelleyEraForecast` instances for `AllegraEra`.
99
* Remove `NoThunks` instance for `AllegraUtxoPredFailure`
10+
* Add `ToJSON` and `FromJSON` instances for `Timelock era`
11+
* Export `allegraBasedEraNativeScriptToJSON` and `allegraBasedEraNativeScriptJSONParser` from `Cardano.Ledger.Allegra.Scripts`
1012

1113
### `cddl`
1214

eras/allegra/impl/src/Cardano/Ledger/Allegra/Scripts.hs

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{-# LANGUAGE AllowAmbiguousTypes #-}
22
{-# LANGUAGE CPP #-}
33
{-# LANGUAGE DataKinds #-}
4-
{-# LANGUAGE DefaultSignatures #-}
54
{-# LANGUAGE DeriveAnyClass #-}
65
{-# LANGUAGE DeriveGeneric #-}
76
{-# LANGUAGE DerivingVia #-}
@@ -58,10 +57,14 @@ module Cardano.Ledger.Allegra.Scripts (
5857
ltePosInfty,
5958
invalidBeforeL,
6059
invalidHereAfterL,
60+
61+
-- ** JSON utilities
62+
allegraBasedEraNativeScriptToJSON,
63+
allegraBasedEraNativeScriptJSONParser,
6164
) where
6265

6366
import Cardano.Ledger.Allegra.Era (AllegraEra)
64-
import Cardano.Ledger.BaseTypes (StrictMaybe (..), maybeToStrictMaybe)
67+
import Cardano.Ledger.BaseTypes (StrictMaybe (..), kindObject, maybeToStrictMaybe)
6568
import Cardano.Ledger.Binary (
6669
Annotator,
6770
DecCBOR (decCBOR),
@@ -98,20 +101,26 @@ import Cardano.Ledger.MemoBytes.Internal (mkMemoBytesShort)
98101
import Cardano.Ledger.Shelley.Scripts (
99102
ShelleyEraScript (..),
100103
nativeMultiSigTag,
104+
shelleyBasedEraNativeScriptJSONParser,
105+
shelleyBasedEraNativeScriptToJSON,
101106
pattern RequireAllOf,
102107
pattern RequireAnyOf,
103108
pattern RequireMOf,
104109
pattern RequireSignature,
105110
)
106111
import Cardano.Slotting.Slot (SlotNo (..))
112+
import Control.Applicative ((<|>))
107113
import Control.DeepSeq (NFData (..))
108-
import Data.Aeson (FromJSON (..), ToJSON (..), withObject, (.:?), (.=))
114+
import Data.Aeson (FromJSON (..), ToJSON (..), withObject, (.:), (.:?), (.=))
109115
import qualified Data.Aeson as Aeson
116+
import Data.Aeson.Types (Parser)
110117
import Data.Foldable as F (foldl')
111-
import Data.MemPack
118+
import Data.MemPack (MemPack (packM, packedByteCount, unpackM))
119+
import Data.Proxy (Proxy (Proxy))
112120
import Data.Sequence.Strict as Seq (StrictSeq (Empty, (:<|)))
113121
import qualified Data.Sequence.Strict as SSeq
114122
import qualified Data.Set as Set (Set, member)
123+
import Data.Typeable (typeRep)
115124
import GHC.Generics (Generic)
116125
import Lens.Micro (Lens', lens)
117126
import NoThunks.Class (NoThunks (..))
@@ -265,6 +274,48 @@ deriving instance Show (Timelock era)
265274
instance EqRaw (Timelock era) where
266275
eqRaw = eqTimelockRaw
267276

277+
instance (AllegraEraScript era, NativeScript era ~ Timelock era) => ToJSON (Timelock era) where
278+
toJSON =
279+
let go = allegraBasedEraNativeScriptToJSON go
280+
in Aeson.Object . go
281+
282+
allegraBasedEraNativeScriptToJSON ::
283+
AllegraEraScript era =>
284+
(NativeScript era -> Aeson.Object) ->
285+
NativeScript era ->
286+
Aeson.Object
287+
allegraBasedEraNativeScriptToJSON childNativeScriptToObject script =
288+
shelleyBasedEraNativeScriptToJSON childNativeScriptToObject script
289+
<> case script of
290+
RequireTimeStart s ->
291+
kindObject "TimeStart" ["slot" .= s]
292+
RequireTimeExpire s ->
293+
kindObject "TimeExpire" ["slot" .= s]
294+
_ -> mempty
295+
296+
instance (AllegraEraScript era, NativeScript era ~ Timelock era) => FromJSON (Timelock era) where
297+
parseJSON = allegraBasedEraNativeScriptJSONParser
298+
299+
allegraBasedEraNativeScriptJSONParser ::
300+
forall era.
301+
AllegraEraScript era =>
302+
Aeson.Value ->
303+
Parser (NativeScript era)
304+
allegraBasedEraNativeScriptJSONParser value =
305+
shelleyBasedEraNativeScriptJSONParser value
306+
<|> Aeson.withObject
307+
typeName
308+
( \o -> do
309+
t <- o .: "kind" :: Parser String
310+
case t of
311+
"TimeStart" -> RequireTimeStart @era <$> o .: "slot"
312+
"TimeExpire" -> RequireTimeExpire @era <$> o .: "slot"
313+
_ -> fail $ "Unknown " <> typeName <> " kind: " <> t
314+
)
315+
value
316+
where
317+
typeName = show $ typeRep (Proxy @(NativeScript era))
318+
268319
upgradeMultiSig :: NativeScript ShelleyEra -> NativeScript AllegraEra
269320
upgradeMultiSig = \case
270321
RequireSignature keyHash -> RequireSignature keyHash

eras/allegra/impl/testlib/Test/Cardano/Ledger/Allegra/Arbitrary.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ sizedTimelock ::
5959
sizedTimelock 0 = RequireSignature <$> arbitrary
6060
sizedTimelock n =
6161
oneof $
62-
sizedNativeScriptGens n
62+
sizedNativeScriptGens n sizedTimelock
6363
<> [ RequireTimeStart <$> arbitrary
6464
, RequireTimeExpire <$> arbitrary
6565
]

eras/alonzo/impl/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
* Remove `NoThunks (ContextError era)` constraint from `EraPlutusContext` class
4141
* Remove `NoThunks` deriving instance for `CollectError`
4242
* Make `AlonzoContextError` constructors lazy
43+
* Add `ToJSON` and `FromJSON` instances for `AlonzoScript era`
4344

4445
### `cddl`
4546

eras/alonzo/impl/cardano-ledger-alonzo.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ library
9090
FailT,
9191
aeson >=2.2,
9292
base >=4.18 && <5,
93+
base16-bytestring,
9394
base64-bytestring,
9495
bytestring,
9596
cardano-base >=0.1.1.0,

eras/alonzo/impl/src/Cardano/Ledger/Alonzo/Plutus/Context.hs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import Cardano.Ledger.Alonzo.Scripts (
5252
PlutusPurpose,
5353
PlutusScript (..),
5454
)
55-
import Cardano.Ledger.BaseTypes (ProtVer (..), kindObject)
55+
import Cardano.Ledger.BaseTypes (ProtVer (..), kindObjectValue)
5656
import Cardano.Ledger.Binary (DecCBOR (..), EncCBOR (..))
5757
import Cardano.Ledger.Binary.Coders
5858
import Cardano.Ledger.Core
@@ -384,19 +384,22 @@ instance
384384
where
385385
toJSON = \case
386386
NoRedeemer sPurpose ->
387-
kindObject "CollectError" $
387+
kindObjectValue
388+
"CollectError"
388389
[ "error" .= String "NoRedeemer"
389390
, "plutusPurpose" .= toJSON sPurpose
390391
]
391392
NoWitness sHash ->
392-
kindObject "CollectError" $
393+
kindObjectValue
394+
"CollectError"
393395
[ "error" .= String "NoWitness"
394396
, "scriptHash" .= toJSON sHash
395397
]
396398
NoCostModel lang ->
397-
kindObject "CollectError" $
399+
kindObjectValue
400+
"CollectError"
398401
[ "error" .= String "NoCostModel"
399402
, "language" .= toJSON lang
400403
]
401404
BadTranslation err ->
402-
kindObject "BadTranslation" ["error" .= toJSON err]
405+
kindObjectValue "BadTranslation" ["error" .= toJSON err]

eras/alonzo/impl/src/Cardano/Ledger/Alonzo/Rules/Utxos.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import Cardano.Ledger.BaseTypes (
4949
ShelleyBase,
5050
StrictMaybe,
5151
epochInfo,
52-
kindObject,
52+
kindObjectValue,
5353
systemStart,
5454
)
5555
import Cardano.Ledger.Binary (
@@ -314,7 +314,7 @@ instance DecCBOR FailureDescription where
314314

315315
instance ToJSON FailureDescription where
316316
toJSON (PlutusFailure t _bs) =
317-
kindObject
317+
kindObjectValue
318318
"FailureDescription"
319319
[ "error" .= Aeson.String "PlutusFailure"
320320
, "description" .= t
@@ -347,11 +347,11 @@ instance DecCBOR TagMismatchDescription where
347347
instance ToJSON TagMismatchDescription where
348348
toJSON = \case
349349
PassedUnexpectedly ->
350-
kindObject
350+
kindObjectValue
351351
"TagMismatchDescription"
352352
["error" .= Aeson.String "PassedUnexpectedly"]
353353
FailedUnexpectedly forReasons ->
354-
kindObject
354+
kindObjectValue
355355
"TagMismatchDescription"
356356
[ "error" .= Aeson.String "FailedUnexpectedly"
357357
, "reconstruction" .= forReasons

eras/alonzo/impl/src/Cardano/Ledger/Alonzo/Scripts.hs

Lines changed: 86 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ module Cardano.Ledger.Alonzo.Scripts (
6565
import Cardano.Ledger.Allegra.Scripts
6666
import Cardano.Ledger.Alonzo.Era (AlonzoEra)
6767
import Cardano.Ledger.Alonzo.TxCert ()
68-
import Cardano.Ledger.BaseTypes (ProtVer (..), kindObject)
68+
import Cardano.Ledger.BaseTypes (ProtVer (..), kindObjectValue)
6969
import Cardano.Ledger.Binary (
7070
Annotator,
7171
CBORGroup (..),
@@ -88,7 +88,6 @@ import Cardano.Ledger.Binary.Coders (
8888
(!>),
8989
(<*!),
9090
)
91-
import Cardano.Ledger.Binary.Plain (serializeAsHexText)
9291
import Cardano.Ledger.Core
9392
import Cardano.Ledger.Mary.Value (PolicyID)
9493
import Cardano.Ledger.MemoBytes (EqRaw (..))
@@ -102,6 +101,7 @@ import Cardano.Ledger.Plutus.Language (
102101
SLanguage (..),
103102
asSLanguage,
104103
isValidPlutus,
104+
languageFromText,
105105
plutusLanguage,
106106
plutusSLanguage,
107107
withSLanguage,
@@ -110,12 +110,28 @@ import Cardano.Ledger.Shelley.Scripts (ShelleyEraScript (..), nativeMultiSigTag)
110110
import Cardano.Ledger.TxIn (TxIn)
111111
import Control.DeepSeq (NFData (..), deepseq, rwhnf)
112112
import Control.Monad (guard, (>=>))
113-
import Data.Aeson (ToJSON (..), Value (String), object, (.=))
113+
import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON), object, withObject, (.:), (.=))
114+
import qualified Data.Aeson as Aeson
115+
import Data.Aeson.Types (Parser)
114116
import qualified Data.ByteString as BS
117+
import qualified Data.ByteString.Base16 as BS16
118+
import qualified Data.ByteString.Base64 as BS64
119+
import qualified Data.ByteString.Short as SBS
115120
import Data.Kind (Type)
116121
import qualified Data.Map.Strict as Map
117122
import Data.Maybe (fromJust, isJust)
118-
import Data.MemPack
123+
import Data.MemPack (
124+
MemPack,
125+
packM,
126+
packTagM,
127+
packedByteCount,
128+
packedTagByteCount,
129+
unknownTagM,
130+
unpackM,
131+
unpackTagM,
132+
)
133+
import Data.Text (Text)
134+
import Data.Text.Encoding (decodeLatin1, encodeUtf8)
119135
import Data.Typeable
120136
import Data.Word (Word32)
121137
import GHC.Generics (Generic)
@@ -296,6 +312,11 @@ instance (ToJSON ix, ToJSON it) => ToJSON (AsIxItem ix it) where
296312
, "item" .= toJSON it
297313
]
298314

315+
instance FromJSON ix => FromJSON (AsIx ix it) where
316+
parseJSON =
317+
withObject "AsIx" $ \o ->
318+
AsIx <$> o .: "index"
319+
299320
toAsPurpose :: f ix it -> AsPurpose ix it
300321
toAsPurpose = const AsPurpose
301322

@@ -406,7 +427,25 @@ instance
406427
AlonzoCertifying n -> kindObjectWithValue "AlonzoCertifying" n
407428
AlonzoRewarding n -> kindObjectWithValue "AlonzoRewarding" n
408429
where
409-
kindObjectWithValue name n = kindObject name ["value" .= n]
430+
kindObjectWithValue name n = kindObjectValue name ["value" .= n]
431+
432+
instance
433+
( FromJSON (AsIx Word32 TxIn)
434+
, FromJSON (AsIx Word32 PolicyID)
435+
, FromJSON (AsIx Word32 (TxCert era))
436+
, FromJSON (AsIx Word32 AccountAddress)
437+
) =>
438+
FromJSON (AlonzoPlutusPurpose AsIx era)
439+
where
440+
parseJSON =
441+
withObject "AlonzoPlutusPurpose" $ \o -> do
442+
kind <- o .: "kind" :: Parser Text
443+
case kind of
444+
"AlonzoSpending" -> AlonzoSpending <$> o .: "value"
445+
"AlonzoMinting" -> AlonzoMinting <$> o .: "value"
446+
"AlonzoCertifying" -> AlonzoCertifying <$> o .: "value"
447+
"AlonzoRewarding" -> AlonzoRewarding <$> o .: "value"
448+
_ -> fail $ "Unknown AlonzoPlutusPurpose kind: " <> show kind
410449

411450
pattern SpendingPurpose ::
412451
AlonzoEraScript era => f Word32 TxIn -> PlutusPurpose f era
@@ -579,8 +618,41 @@ instance
579618
where
580619
eqRaw = eqAlonzoScriptRaw
581620

582-
instance AlonzoEraScript era => ToJSON (AlonzoScript era) where
583-
toJSON = String . serializeAsHexText
621+
instance (AlonzoEraScript era, Script era ~ AlonzoScript era) => ToJSON (AlonzoScript era) where
622+
toJSON script =
623+
object $
624+
[ "scriptHash" .= hashScript script
625+
, "scriptBase16Tag" .= toBase16Text (scriptPrefixTag script)
626+
, "scriptBase64Bytes" .= toBase64Text (originalBytes script)
627+
, "scriptType"
628+
.= case script of
629+
NativeScript _ -> Aeson.String "NativeScript"
630+
PlutusScript plutusScript -> toJSON (withPlutusScript plutusScript plutusLanguage)
631+
]
632+
++ case script of
633+
NativeScript nativeScript -> ["nativeScriptContents" .= toJSON nativeScript]
634+
PlutusScript _ -> []
635+
where
636+
toBase64Text = decodeLatin1 . BS64.encode
637+
toBase16Text = ("0x" <>) . decodeLatin1 . BS16.encode
638+
639+
instance AlonzoEraScript era => FromJSON (AlonzoScript era) where
640+
parseJSON = withObject typeName $ \o -> do
641+
scriptType <- o .: "scriptType" :: Parser Text
642+
case scriptType of
643+
"NativeScript" -> NativeScript <$> o .: "nativeScriptContents"
644+
_ -> do
645+
lang <- languageFromText scriptType
646+
b64 <- o .: "scriptBase64Bytes" :: Parser Text
647+
bytes <-
648+
either
649+
(fail . ("AlonzoScript: invalid base64: " <>))
650+
pure
651+
(BS64.decode (encodeUtf8 b64))
652+
let pb = PlutusBinary (SBS.toShort bytes)
653+
PlutusScript <$> mkBinaryPlutusScript lang pb
654+
where
655+
typeName = show $ typeRep (Proxy @(AlonzoScript era))
584656

585657
-- | It might seem that this instance unnecessarily utilizes a zero Tag, but it is needed for
586658
-- forward compatibility with plutus scripts from future eras.
@@ -620,12 +692,13 @@ instance AlonzoEraScript era => ToCBOR (AlonzoScript era) where
620692
encodeScript :: AlonzoEraScript era => AlonzoScript era -> Encode Open (AlonzoScript era)
621693
encodeScript = \case
622694
NativeScript i -> Sum NativeScript 0 !> To i
623-
PlutusScript plutusScript -> withPlutusScript plutusScript $ \plutus@(Plutus pb) ->
624-
case plutusSLanguage plutus of
625-
SPlutusV1 -> Sum (PlutusScript . fromJust . mkPlutusScript . Plutus @'PlutusV1) 1 !> To pb
626-
SPlutusV2 -> Sum (PlutusScript . fromJust . mkPlutusScript . Plutus @'PlutusV2) 2 !> To pb
627-
SPlutusV3 -> Sum (PlutusScript . fromJust . mkPlutusScript . Plutus @'PlutusV3) 3 !> To pb
628-
SPlutusV4 -> Sum (PlutusScript . fromJust . mkPlutusScript . Plutus @'PlutusV4) 4 !> To pb
695+
PlutusScript plutusScript ->
696+
withPlutusScript plutusScript $ \plutus@(Plutus pb) ->
697+
case plutusSLanguage plutus of
698+
SPlutusV1 -> Sum (PlutusScript . fromJust . mkPlutusScript . Plutus @'PlutusV1) 1 !> To pb
699+
SPlutusV2 -> Sum (PlutusScript . fromJust . mkPlutusScript . Plutus @'PlutusV2) 2 !> To pb
700+
SPlutusV3 -> Sum (PlutusScript . fromJust . mkPlutusScript . Plutus @'PlutusV3) 3 !> To pb
701+
SPlutusV4 -> Sum (PlutusScript . fromJust . mkPlutusScript . Plutus @'PlutusV4) 4 !> To pb
629702

630703
instance AlonzoEraScript era => DecCBOR (Annotator (AlonzoScript era)) where
631704
decCBOR = decode (Summands "AlonzoScript" decodeScript)

eras/babbage/impl/src/Cardano/Ledger/Babbage/TxInfo.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import Cardano.Ledger.BaseTypes (
5353
ProtVer,
5454
StrictMaybe (..),
5555
isSJust,
56-
kindObject,
56+
kindObjectValue,
5757
)
5858
import Cardano.Ledger.Binary (DecCBOR (..), EncCBOR (..))
5959
import Cardano.Ledger.Binary.Coders (
@@ -301,7 +301,7 @@ instance ToJSON (PlutusPurpose AsIx era) => ToJSON (BabbageContextError era) whe
301301
ByronTxOutInContext txOutSource ->
302302
String $ "Byron UTxO being created or spent: " <> txOutSourceToText txOutSource
303303
RedeemerPointerPointsToNothing ptr ->
304-
kindObject "RedeemerPointerPointsToNothing" ["ptr" .= toJSON ptr]
304+
kindObjectValue "RedeemerPointerPointsToNothing" ["ptr" .= toJSON ptr]
305305
InlineDatumsNotSupported txOutSource ->
306306
String $ "Inline datums not supported, output source: " <> txOutSourceToText txOutSource
307307
ReferenceScriptsNotSupported txOutSource ->

eras/conway/impl/src/Cardano/Ledger/Conway/Scripts.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import Cardano.Ledger.Alonzo.Scripts (
3535
)
3636
import Cardano.Ledger.Babbage.Core
3737
import Cardano.Ledger.Babbage.Scripts (PlutusScript (..))
38-
import Cardano.Ledger.BaseTypes (kindObject)
38+
import Cardano.Ledger.BaseTypes (kindObjectValue)
3939
import Cardano.Ledger.Binary (
4040
CBORGroup (..),
4141
DecCBOR (decCBOR),
@@ -310,7 +310,7 @@ instance
310310
ConwayVoting n -> kindObjectWithValue "ConwayVoting" n
311311
ConwayProposing n -> kindObjectWithValue "ConwayProposing" n
312312
where
313-
kindObjectWithValue name n = kindObject name ["value" .= n]
313+
kindObjectWithValue name n = kindObjectValue name ["value" .= n]
314314

315315
pattern VotingPurpose ::
316316
ConwayEraScript era => f Word32 Voter -> PlutusPurpose f era

0 commit comments

Comments
 (0)