-
Notifications
You must be signed in to change notification settings - Fork 721
/
Friendly.hs
318 lines (286 loc) · 11.4 KB
/
Friendly.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DisambiguateRecordFields #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | User-friendly pretty-printing for textual user interfaces (TUI)
module Cardano.CLI.Run.Friendly (friendlyTxBodyBS) where
import Cardano.Prelude
import Data.Aeson (Value (..), object, toJSON, (.=))
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
import qualified Data.ByteString.Char8 as BSC
import qualified Data.Map.Strict as Map
import qualified Data.Text as Text
import Data.Yaml (array)
import Data.Yaml.Pretty (defConfig, encodePretty, setConfCompare)
import Cardano.Api as Api
import Cardano.Api.Shelley (Address (ShelleyAddress), StakeAddress (..))
import Cardano.Ledger.Crypto (Crypto)
import qualified Cardano.Ledger.Shelley.API as Shelley
import Cardano.CLI.Helpers (textShow)
friendlyTxBodyBS :: CardanoEra era -> TxBody era -> ByteString
friendlyTxBodyBS era =
encodePretty (setConfCompare compare defConfig) . friendlyTxBody era
friendlyTxBody :: CardanoEra era -> TxBody era -> Aeson.Value
friendlyTxBody
era
(TxBody
TxBodyContent
{ txAuxScripts
, txCertificates
, txFee
, txIns
, txMetadata
, txMintValue
, txOuts
, txUpdateProposal
, txValidityRange
, txWithdrawals
}) =
object
[ "auxiliary scripts" .= friendlyAuxScripts txAuxScripts
, "certificates" .= friendlyCertificates txCertificates
, "era" .= era
, "fee" .= friendlyFee txFee
, "inputs" .= friendlyInputs txIns
, "metadata" .= friendlyMetadata txMetadata
, "mint" .= friendlyMintValue txMintValue
, "outputs" .= map friendlyTxOut txOuts
, "update proposal" .= friendlyUpdateProposal txUpdateProposal
, "validity range" .= friendlyValidityRange era txValidityRange
, "withdrawals" .= friendlyWithdrawals txWithdrawals
]
-- | Special case of validity range:
-- in Shelley, upper bound is TTL, and no lower bound
pattern ShelleyTtl
:: SlotNo -> (TxValidityLowerBound era, TxValidityUpperBound era)
pattern ShelleyTtl ttl <-
( TxValidityNoLowerBound
, TxValidityUpperBound ValidityUpperBoundInShelleyEra ttl
)
friendlyValidityRange
:: CardanoEra era
-> (TxValidityLowerBound era, TxValidityUpperBound era)
-> Aeson.Value
friendlyValidityRange era = \case
ShelleyTtl ttl -> object ["time to live" .= ttl]
(lowerBound, upperBound)
| isLowerBoundSupported || isUpperBoundSupported ->
object
[ "lower bound" .=
case lowerBound of
TxValidityNoLowerBound -> Null
TxValidityLowerBound _ s -> toJSON s
, "upper bound" .=
case upperBound of
TxValidityNoUpperBound _ -> Null
TxValidityUpperBound _ s -> toJSON s
]
| otherwise -> Null
where
isLowerBoundSupported = isJust $ validityLowerBoundSupportedInEra era
isUpperBoundSupported = isJust $ validityUpperBoundSupportedInEra era
friendlyWithdrawals :: TxWithdrawals ViewTx era -> Aeson.Value
friendlyWithdrawals TxWithdrawalsNone = Null
friendlyWithdrawals (TxWithdrawals _ withdrawals) =
array
[ object
[ "address" .= serialiseAddress addr
, "network" .= net
, "credential" .= cred
, "amount" .= friendlyLovelace amount
]
| (addr@(StakeAddress net cred), amount, _) <- withdrawals
]
friendlyTxOut :: TxOut CtxTx era -> Aeson.Value
friendlyTxOut (TxOut addr amount mdatum) =
case addr of
AddressInEra ByronAddressInAnyEra byronAdr ->
object [ "address era" .= String "Byron"
, "address" .= serialiseAddress byronAdr
, "amount" .= friendlyTxOutValue amount
]
AddressInEra (ShelleyAddressInEra sbe) saddr@(ShelleyAddress net cred stake) ->
let preAlonzo :: [Aeson.Pair]
preAlonzo =
[ "address era" .= Aeson.String "Shelley"
, "network" .= net
, "payment credential" .= cred
, "stake reference" .= friendlyStakeReference stake
, "address" .= serialiseAddress saddr
, "amount" .= friendlyTxOutValue amount
]
datum :: ShelleyBasedEra era -> [Aeson.Pair]
datum ShelleyBasedEraShelley = []
datum ShelleyBasedEraAllegra = []
datum ShelleyBasedEraMary = []
datum ShelleyBasedEraAlonzo = ["datum" .= renderDatum mdatum]
in object $ preAlonzo ++ datum sbe
where
renderDatum :: TxOutDatum CtxTx era -> Aeson.Value
renderDatum TxOutDatumNone = Aeson.Null
renderDatum (TxOutDatumHash _ h) =
Aeson.String $ serialiseToRawBytesHexText h
renderDatum (TxOutDatum _ sData) =
scriptDataToJson ScriptDataJsonDetailedSchema sData
friendlyStakeReference :: Crypto crypto => Shelley.StakeReference crypto -> Aeson.Value
friendlyStakeReference = \case
Shelley.StakeRefBase cred -> toJSON cred
Shelley.StakeRefNull -> Null
Shelley.StakeRefPtr ptr -> toJSON ptr
friendlyUpdateProposal :: TxUpdateProposal era -> Aeson.Value
friendlyUpdateProposal = \case
TxUpdateProposalNone -> Null
TxUpdateProposal _ (UpdateProposal parameterUpdates epoch) ->
object
[ "epoch" .= epoch
, "updates" .=
[ object
[ "genesis key hash" .= serialiseToRawBytesHexText genesisKeyHash
, "update" .= friendlyProtocolParametersUpdate parameterUpdate
]
| (genesisKeyHash, parameterUpdate) <- Map.assocs parameterUpdates
]
]
friendlyProtocolParametersUpdate :: ProtocolParametersUpdate -> Aeson.Value
friendlyProtocolParametersUpdate
ProtocolParametersUpdate
{ protocolUpdateProtocolVersion
, protocolUpdateDecentralization
, protocolUpdateExtraPraosEntropy
, protocolUpdateMaxBlockHeaderSize
, protocolUpdateMaxBlockBodySize
, protocolUpdateMaxTxSize
, protocolUpdateTxFeeFixed
, protocolUpdateTxFeePerByte
, protocolUpdateMinUTxOValue
, protocolUpdateStakeAddressDeposit
, protocolUpdateStakePoolDeposit
, protocolUpdateMinPoolCost
, protocolUpdatePoolRetireMaxEpoch
, protocolUpdateStakePoolTargetNum
, protocolUpdatePoolPledgeInfluence
, protocolUpdateMonetaryExpansion
, protocolUpdateTreasuryCut
, protocolUpdateUTxOCostPerWord
, protocolUpdateCollateralPercent
, protocolUpdateMaxBlockExUnits
, protocolUpdateMaxCollateralInputs
, protocolUpdateMaxTxExUnits
, protocolUpdateMaxValueSize
, protocolUpdatePrices
} =
object . catMaybes $
[ protocolUpdateProtocolVersion <&> \(major, minor) ->
"protocol version" .= (textShow major <> "." <> textShow minor)
, protocolUpdateDecentralization <&>
("decentralization parameter" .=) . friendlyRational
, protocolUpdateExtraPraosEntropy <&>
("extra entropy" .=) . maybe "reset" toJSON
, protocolUpdateMaxBlockHeaderSize <&> ("max block header size" .=)
, protocolUpdateMaxBlockBodySize<&> ("max block body size" .=)
, protocolUpdateMaxTxSize <&> ("max transaction size" .=)
, protocolUpdateTxFeeFixed <&> ("transaction fee constant" .=)
, protocolUpdateTxFeePerByte <&> ("transaction fee linear per byte" .=)
, protocolUpdateMinUTxOValue <&> ("min UTxO value" .=) . friendlyLovelace
, protocolUpdateStakeAddressDeposit <&>
("key registration deposit" .=) . friendlyLovelace
, protocolUpdateStakePoolDeposit <&>
("pool registration deposit" .=) . friendlyLovelace
, protocolUpdateMinPoolCost <&> ("min pool cost" .=) . friendlyLovelace
, protocolUpdatePoolRetireMaxEpoch <&> ("pool retirement epoch boundary" .=)
, protocolUpdateStakePoolTargetNum <&> ("number of pools" .=)
, protocolUpdatePoolPledgeInfluence <&>
("pool influence" .=) . friendlyRational
, protocolUpdateMonetaryExpansion <&>
("monetary expansion" .=) . friendlyRational
, protocolUpdateTreasuryCut <&> ("treasury expansion" .=) . friendlyRational
, protocolUpdateUTxOCostPerWord <&>
("UTxO storage cost per unit" .=) . friendlyLovelace
, protocolUpdateCollateralPercent <&>
("collateral inputs share" .=) . (<> "%") . textShow
, protocolUpdateMaxBlockExUnits <&> ("max block execution units" .=)
, protocolUpdateMaxCollateralInputs <&> ("max collateral inputs" .=)
, protocolUpdateMaxTxExUnits <&> ("max transaction execution units" .=)
, protocolUpdateMaxValueSize <&> ("max value size" .=)
, protocolUpdatePrices <&> ("execution prices" .=) . friendlyPrices
]
friendlyPrices :: ExecutionUnitPrices -> Aeson.Value
friendlyPrices ExecutionUnitPrices{priceExecutionMemory, priceExecutionSteps} =
object
[ "memory" .= friendlyRational priceExecutionMemory
, "steps" .= friendlyRational priceExecutionSteps
]
friendlyCertificates :: TxCertificates ViewTx era -> Aeson.Value
friendlyCertificates = \case
TxCertificatesNone -> Null
TxCertificates _ cs _ -> toJSON $ map textShow cs
friendlyRational :: Rational -> Aeson.Value
friendlyRational r =
String $
case d of
1 -> textShow n
_ -> textShow n <> "/" <> textShow d
where
n = numerator r
d = denominator r
friendlyFee :: TxFee era -> Aeson.Value
friendlyFee = \case
TxFeeImplicit _ -> "implicit"
TxFeeExplicit _ fee -> friendlyLovelace fee
friendlyLovelace :: Lovelace -> Aeson.Value
friendlyLovelace (Lovelace value) = String $ textShow value <> " Lovelace"
friendlyMintValue :: TxMintValue ViewTx era -> Aeson.Value
friendlyMintValue = \case
TxMintNone -> Null
TxMintValue _ v _ -> friendlyValue v
friendlyTxOutValue :: TxOutValue era -> Aeson.Value
friendlyTxOutValue = \case
TxOutAdaOnly _ lovelace -> friendlyLovelace lovelace
TxOutValue _ v -> friendlyValue v
friendlyValue :: Api.Value -> Aeson.Value
friendlyValue v =
object
[ case bundle of
ValueNestedBundleAda q -> "lovelace" .= q
ValueNestedBundle policy assets ->
friendlyPolicyId policy .= friendlyAssets assets
| bundle <- bundles
]
where
ValueNestedRep bundles = valueToNestedRep v
friendlyPolicyId = ("policy " <>) . serialiseToRawBytesHexText
friendlyAssets = Map.mapKeys friendlyAssetName
friendlyAssetName = \case
"" -> "default asset"
name@(AssetName nameBS) ->
"asset " <> serialiseToRawBytesHexText name <> nameAsciiSuffix
where
nameAsciiSuffix
| nameIsAscii = " (" <> nameAscii <> ")"
| otherwise = ""
nameIsAscii = BSC.all (\c -> isAscii c && isAlphaNum c) nameBS
nameAscii = Text.pack $ BSC.unpack nameBS
friendlyMetadata :: TxMetadataInEra era -> Aeson.Value
friendlyMetadata = \case
TxMetadataNone -> Null
TxMetadataInEra _ (TxMetadata m) -> toJSON $ friendlyMetadataValue <$> m
friendlyMetadataValue :: TxMetadataValue -> Aeson.Value
friendlyMetadataValue = \case
TxMetaNumber int -> toJSON int
TxMetaBytes bytes -> String $ textShow bytes
TxMetaList lst -> array $ map friendlyMetadataValue lst
TxMetaMap m ->
array
[array [friendlyMetadataValue k, friendlyMetadataValue v] | (k, v) <- m]
TxMetaText text -> toJSON text
friendlyAuxScripts :: TxAuxScripts era -> Aeson.Value
friendlyAuxScripts = \case
TxAuxScriptsNone -> Null
TxAuxScripts _ scripts -> String $ textShow scripts
friendlyInputs :: [(TxIn, build)] -> Aeson.Value
friendlyInputs = toJSON . map fst