Skip to content

Commit

Permalink
cardano-cli transaction view: Add friendly certificate printing
Browse files Browse the repository at this point in the history
  • Loading branch information
cblp committed Jan 27, 2022
1 parent 70b9874 commit 11c8c8f
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 82 deletions.
17 changes: 13 additions & 4 deletions cardano-api/src/Cardano/Api/Address.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
Expand Down Expand Up @@ -73,18 +74,17 @@ module Cardano.Api.Address (

import Prelude

import Data.Aeson (FromJSON (..), ToJSON (..), withText)
import Control.Applicative ((<|>))
import Data.Aeson (FromJSON (..), ToJSON (..), withText, (.=))
import qualified Data.Aeson as Aeson
import qualified Data.ByteString.Base58 as Base58
import Data.Char
import Data.Char (isAsciiLower, isAsciiUpper, isDigit)
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Text.Parsec as Parsec
import qualified Text.Parsec.String as Parsec

import Control.Applicative

import qualified Cardano.Chain.Common as Byron
import qualified Cardano.Ledger.Address as Shelley
import qualified Cardano.Ledger.Alonzo.TxInfo as Alonzo
Expand Down Expand Up @@ -474,6 +474,15 @@ data StakeCredential
| StakeCredentialByScript ScriptHash
deriving (Eq, Ord, Show)

instance ToJSON StakeCredential where
toJSON =
Aeson.object
. \case
StakeCredentialByKey keyHash ->
["stakingKeyHash" .= serialiseToRawBytesHexText keyHash]
StakeCredentialByScript scriptHash ->
["stakingScriptHash" .= serialiseToRawBytesHexText scriptHash]

data StakeAddressReference
= StakeAddressByValue StakeCredential
| StakeAddressByPointer StakeAddressPointer
Expand Down
232 changes: 167 additions & 65 deletions cardano-cli/src/Cardano/CLI/Run/Friendly.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ import qualified Data.Text as Text
import Data.Yaml (array)
import Data.Yaml.Pretty (defConfig, encodePretty, setConfCompare)

import Cardano.Ledger.Shelley.TxBody (MIRPot (ReservesMIR, TreasuryMIR))

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.Api.Shelley (Address (ShelleyAddress), StakeAddress (..),
StakeCredential (..), StakePoolParameters (..), fromShelleyPaymentCredential,
fromShelleyStakeCredential, fromShelleyStakeReference)

import Cardano.CLI.Helpers (textShow)

Expand Down Expand Up @@ -90,62 +92,66 @@ friendlyValidityRange era = \case
TxValidityUpperBound _ s -> toJSON s
]
| otherwise -> Null
where
isLowerBoundSupported = isJust $ validityLowerBoundSupportedInEra era
isUpperBoundSupported = isJust $ validityUpperBoundSupportedInEra era
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
[ object $
"address" .= serialiseAddress addr :
"amount" .= friendlyLovelace amount :
friendlyStakeAddress addr
| (addr, amount, _) <- withdrawals
]

friendlyStakeAddress :: StakeAddress -> [(Text, Aeson.Value)]
friendlyStakeAddress (StakeAddress net cred) =
[ "network" .= net
, friendlyStakeCredential $ fromShelleyStakeCredential cred
]

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
object $
case addr of
AddressInEra ByronAddressInAnyEra byronAdr ->
[ "address era" .= String "Byron"
, "address" .= serialiseAddress byronAdr
, "amount" .= friendlyTxOutValue amount
]
AddressInEra (ShelleyAddressInEra sbe) saddr@(ShelleyAddress net cred stake) ->
let preAlonzo :: [Aeson.Pair]
preAlonzo =
friendlyPaymentCredential (fromShelleyPaymentCredential cred) :
[ "address era" .= Aeson.String "Shelley"
, "network" .= net
, "address" .= serialiseAddress saddr
, "amount" .= friendlyTxOutValue amount
, "stake reference" .=
friendlyStakeReference (fromShelleyStakeReference stake)
]

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
datum :: [Aeson.Pair]
datum =
[ "datum" .= renderDatum mdatum
| isJust $ scriptDataSupportedInEra $ shelleyBasedToCardanoEra sbe
]
in preAlonzo ++ datum
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 :: StakeAddressReference -> Aeson.Value
friendlyStakeReference = \case
Shelley.StakeRefBase cred -> toJSON cred
Shelley.StakeRefNull -> Null
Shelley.StakeRefPtr ptr -> toJSON ptr
NoStakeAddress -> Null
StakeAddressByPointer ptr -> String (show ptr)
StakeAddressByValue cred -> object [friendlyStakeCredential cred]

friendlyUpdateProposal :: TxUpdateProposal era -> Aeson.Value
friendlyUpdateProposal = \case
Expand Down Expand Up @@ -236,17 +242,113 @@ friendlyPrices ExecutionUnitPrices{priceExecutionMemory, priceExecutionSteps} =
friendlyCertificates :: TxCertificates ViewTx era -> Aeson.Value
friendlyCertificates = \case
TxCertificatesNone -> Null
TxCertificates _ cs _ -> toJSON $ map textShow cs
TxCertificates _ cs _ -> array $ map friendlyCertificate cs

friendlyCertificate :: Certificate -> Aeson.Value
friendlyCertificate =
object
. (: [])
. \case
-- Stake address certificates
StakeAddressRegistrationCertificate credential ->
"stake address registration"
.= object [friendlyStakeCredential credential]
StakeAddressDeregistrationCertificate credential ->
"stake address deregistration"
.= object [friendlyStakeCredential credential]
StakeAddressDelegationCertificate credential poolId ->
"stake address delegation"
.= object [friendlyStakeCredential credential, "pool" .= poolId]

-- Stake pool certificates
StakePoolRegistrationCertificate parameters ->
"stake pool registration" .= friendlyStakePoolParameters parameters
StakePoolRetirementCertificate poolId epochNo ->
"stake pool retirement" .= object ["pool" .= poolId, "epoch" .= epochNo]

-- Special certificates
GenesisKeyDelegationCertificate
genesisKeyHash
delegateKeyHash
vrfKeyHash ->
"genesis key delegation"
.= object
[ "genesis key hash"
.= serialiseToRawBytesHexText genesisKeyHash,
"delegate key hash"
.= serialiseToRawBytesHexText delegateKeyHash,
"VRF key hash" .= serialiseToRawBytesHexText vrfKeyHash
]
MIRCertificate pot target ->
"MIR" .= object ["pot" .= friendlyMirPot pot, friendlyMirTarget target]

friendlyMirTarget :: MIRTarget -> (Text, Aeson.Value)
friendlyMirTarget = \case
StakeAddressesMIR addresses ->
"target stake addresses" .=
[ object
[ friendlyStakeCredential credential
, "amount" .= friendlyLovelace lovelace
]
| (credential, lovelace) <- addresses
]
SendToReservesMIR amount -> "send to reserves" .= friendlyLovelace amount
SendToTreasuryMIR amount -> "send to treasury" .= friendlyLovelace amount

friendlyStakeCredential :: StakeCredential -> (Text, Aeson.Value)
friendlyStakeCredential = \case
StakeCredentialByKey keyHash ->
"stake credential key hash" .= serialiseToRawBytesHexText keyHash
StakeCredentialByScript scriptHash ->
"stake credential script hash" .= serialiseToRawBytesHexText scriptHash

friendlyPaymentCredential :: PaymentCredential -> (Text, Aeson.Value)
friendlyPaymentCredential = \case
PaymentCredentialByKey keyHash ->
"payment credential key hash" .= serialiseToRawBytesHexText keyHash
PaymentCredentialByScript scriptHash ->
"payment credential script hash" .= serialiseToRawBytesHexText scriptHash

friendlyMirPot :: MIRPot -> Aeson.Value
friendlyMirPot = \case
ReservesMIR -> "reserves"
TreasuryMIR -> "treasury"

friendlyStakePoolParameters :: StakePoolParameters -> Aeson.Value
friendlyStakePoolParameters
StakePoolParameters
{ stakePoolId
, stakePoolVRF
, stakePoolCost
, stakePoolMargin
, stakePoolRewardAccount
, stakePoolPledge
, stakePoolOwners
, stakePoolRelays
, stakePoolMetadata
} =
object
[ "pool" .= stakePoolId
, "VRF key hash" .= serialiseToRawBytesHexText stakePoolVRF
, "cost" .= friendlyLovelace stakePoolCost
, "margin" .= friendlyRational stakePoolMargin
, "reward account" .= object (friendlyStakeAddress stakePoolRewardAccount)
, "pledge" .= friendlyLovelace stakePoolPledge
, "owners (stake key hashes)"
.= map serialiseToRawBytesHexText stakePoolOwners
, "relays" .= map textShow stakePoolRelays
, "metadata" .= fmap textShow stakePoolMetadata
]

friendlyRational :: Rational -> Aeson.Value
friendlyRational r =
String $
case d of
1 -> textShow n
_ -> textShow n <> "/" <> textShow d
where
n = numerator r
d = denominator r
where
n = numerator r
d = denominator r

friendlyFee :: TxFee era -> Aeson.Value
friendlyFee = \case
Expand Down Expand Up @@ -275,24 +377,24 @@ friendlyValue v =
friendlyPolicyId policy .= friendlyAssets assets
| bundle <- bundles
]
where
where

ValueNestedRep bundles = valueToNestedRep v
ValueNestedRep bundles = valueToNestedRep v

friendlyPolicyId = ("policy " <>) . serialiseToRawBytesHexText
friendlyPolicyId = ("policy " <>) . serialiseToRawBytesHexText

friendlyAssets = Map.mapKeys friendlyAssetName
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
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
Expand Down
18 changes: 16 additions & 2 deletions cardano-cli/test/Test/Golden/TxView.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Cardano.Prelude

import Hedgehog (Group (..), Property, checkSequential)
import Hedgehog.Extras (moduleWorkspace, note_, propertyOnce)
import System.FilePath ((</>))

import Test.OptParse (execCardanoCLI, noteTempFile)
import Test.Utilities (diffVsGoldenFile)
Expand Down Expand Up @@ -50,7 +51,18 @@ golden_view_byron =
diffVsGoldenFile result "test/data/golden/byron/transaction-view.out"

golden_view_shelley :: Property
golden_view_shelley =
golden_view_shelley = let
certDir = "test/data/golden/shelley/certificates"
certs =
(certDir </>) <$>
[ "genesis_key_delegation_certificate"
, "mir_certificate"
, "stake_address_deregistration_certificate"
, "stake_address_registration_certificate"
, "stake_pool_deregistration_certificate"
, "stake_pool_registration_certificate"
]
in
propertyOnce $
moduleWorkspace "tmp" $ \tempDir -> do
updateProposalFile <- noteTempFile tempDir "update-proposal"
Expand Down Expand Up @@ -96,7 +108,7 @@ golden_view_shelley =

-- Create transaction body
void $
execCardanoCLI
execCardanoCLI $
[ "transaction", "build-raw"
, "--shelley-era"
, "--tx-in"
Expand All @@ -112,6 +124,8 @@ golden_view_shelley =
, "--update-proposal-file", updateProposalFile
, "--out-file", transactionBodyFile
]
++
["--certificate-file=" <> cert | cert <- certs]

-- View transaction body
result <-
Expand Down
5 changes: 2 additions & 3 deletions cardano-cli/test/data/golden/allegra/transaction-view.out
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ outputs:
address era: Shelley
amount: 99 Lovelace
network: Testnet
payment credential:
key hash: f2998eb67942c4674d01e2cd435e1f17919e095eec43807bb0010313
payment credential key hash: f2998eb67942c4674d01e2cd435e1f17919e095eec43807bb0010313
stake reference:
key hash: c0a060899d6806f810547e2cb66f92d5c817a16af2a20f269e258ee0
stake credential key hash: c0a060899d6806f810547e2cb66f92d5c817a16af2a20f269e258ee0
update proposal: null
validity range:
lower bound: null
Expand Down
Loading

0 comments on commit 11c8c8f

Please sign in to comment.