From a77cc9a18938e387ee794c850c7ea17d369bf852 Mon Sep 17 00:00:00 2001 From: Jordan Millar Date: Mon, 7 Jun 2021 10:20:54 +0100 Subject: [PATCH] Update CLI in order to allow tx outs with datum hashes and other fixes to enable Plutus script submission --- cabal.project | 4 +- .../src/Cardano/Api/ProtocolParameters.hs | 5 +- cardano-api/src/Cardano/Api/ScriptData.hs | 6 ++ cardano-api/src/Cardano/Api/TxInMode.hs | 5 +- .../src/Cardano/CLI/Mary/TxOutParser.hs | 9 ++- .../src/Cardano/CLI/Shelley/Parsers.hs | 33 ++++++-- .../src/Cardano/CLI/Shelley/Run/Query.hs | 6 +- .../Cardano/CLI/Shelley/Run/Transaction.hs | 28 ++++++- cardano-cli/src/Cardano/CLI/Types.hs | 21 ++++- .../example-txin-locking-plutus-script.sh | 80 +++++++++++++++++++ 10 files changed, 169 insertions(+), 28 deletions(-) create mode 100755 scripts/plutus/example-txin-locking-plutus-script.sh diff --git a/cabal.project b/cabal.project index a3d25890bc2..300ea99b2cd 100644 --- a/cabal.project +++ b/cabal.project @@ -11,8 +11,8 @@ packages: cardano-submit-api nix/workbench/cardano-topology -package cardano-api - ghc-options: -Werror +--package cardano-api +-- ghc-options: -Werror package cardano-cli ghc-options: -Werror diff --git a/cardano-api/src/Cardano/Api/ProtocolParameters.hs b/cardano-api/src/Cardano/Api/ProtocolParameters.hs index 3b8f7bd24a0..59bad7299d6 100644 --- a/cardano-api/src/Cardano/Api/ProtocolParameters.hs +++ b/cardano-api/src/Cardano/Api/ProtocolParameters.hs @@ -301,8 +301,8 @@ instance FromJSON ProtocolParameters where <*> o .:? "utxoCostPerWord" <*> o .:? "costModel" .!= Map.empty <*> o .:? "executionUnitPrices" - <*> o .:? "maxTxExecUnits" - <*> o .:? "maxBlockExecUnits" + <*> o .:? "maxTxExecutionUnits" + <*> o .:? "maxBlockExecutionUnits" <*> o .:? "maxValueSize" <*> o .:? "collateralPercentage" <*> o .:? "maxCollateralInputs" @@ -333,6 +333,7 @@ instance ToJSON ProtocolParameters where , "txFeeFixed" .= protocolParamTxFeeFixed , "txFeePerByte" .= protocolParamTxFeePerByte -- Alonzo era: + , "utxoCostPerWord" .= protocolParamUTxOCostPerWord , "costModels" .= protocolParamCostModels , "executionUnitPrices" .= protocolParamPrices , "maxTxExecutionUnits" .= protocolParamMaxTxExUnits diff --git a/cardano-api/src/Cardano/Api/ScriptData.hs b/cardano-api/src/Cardano/Api/ScriptData.hs index 5874333d682..e54295e47f8 100644 --- a/cardano-api/src/Cardano/Api/ScriptData.hs +++ b/cardano-api/src/Cardano/Api/ScriptData.hs @@ -27,6 +27,7 @@ module Cardano.Api.ScriptData ( -- * Data family instances AsType(..), Hash(..), + hashScriptData, ) where import Prelude @@ -60,6 +61,7 @@ import qualified Cardano.Crypto.Hash.Class as Crypto import qualified Cardano.Ledger.SafeHash as Ledger import Ouroboros.Consensus.Shelley.Eras (StandardCrypto) import qualified Cardano.Ledger.Alonzo.Data as Alonzo +import qualified Cardano.Ledger.Alonzo as Alonzo import qualified Plutus.V1.Ledger.Api as Plutus import Cardano.Api.Eras @@ -113,6 +115,10 @@ instance ToJSON (Hash ScriptData) where instance Aeson.ToJSONKey (Hash ScriptData) where toJSONKey = Aeson.toJSONKeyText serialiseToRawBytesHexText +hashScriptData :: ScriptData -> Hash ScriptData +hashScriptData sd = + let aData = toAlonzoData sd :: Alonzo.Data (Alonzo.AlonzoEra StandardCrypto) + in ScriptDataHash $ Alonzo.hashData aData -- ---------------------------------------------------------------------------- -- Conversion functions diff --git a/cardano-api/src/Cardano/Api/TxInMode.hs b/cardano-api/src/Cardano/Api/TxInMode.hs index f2e3e4bdf50..fad91ce0a95 100644 --- a/cardano-api/src/Cardano/Api/TxInMode.hs +++ b/cardano-api/src/Cardano/Api/TxInMode.hs @@ -102,11 +102,10 @@ toConsensusGenTx (TxInMode (ShelleyTx _ tx) MaryEraInCardanoMode) = where tx' = Consensus.mkShelleyTx tx -toConsensusGenTx (TxInMode (ShelleyTx _ _tx) AlonzoEraInCardanoMode) = +toConsensusGenTx (TxInMode (ShelleyTx _ tx) AlonzoEraInCardanoMode) = Consensus.HardForkGenTx (Consensus.OneEraGenTx (S (S (S (S (Z tx')))))) where - tx' = error "toConsensusGenTx: Alonzo not implemented yet" - -- Consensus needs to expose a function that can make create Alonzo txs + tx' = Consensus.mkShelleyTx tx diff --git a/cardano-cli/src/Cardano/CLI/Mary/TxOutParser.hs b/cardano-cli/src/Cardano/CLI/Mary/TxOutParser.hs index f393276751d..735a15eed30 100644 --- a/cardano-cli/src/Cardano/CLI/Mary/TxOutParser.hs +++ b/cardano-cli/src/Cardano/CLI/Mary/TxOutParser.hs @@ -7,25 +7,26 @@ import Prelude import Data.Char (isAsciiLower, isAsciiUpper, isDigit) import Data.Text (Text) import qualified Data.Text as Text +import Control.Applicative -import Control.Applicative (some) import Text.Parsec (option, satisfy, ()) import Text.Parsec.Char (char, spaces) import Text.Parsec.String (Parser) import Cardano.Api (AddressAny (..), AsType (..), deserialiseAddress) import Cardano.CLI.Mary.ValueParser (parseValue) -import Cardano.CLI.Types (TxOutAnyEra (..)) +import Cardano.CLI.Types (TxOutAnyEra' (..)) -parseTxOutAnyEra :: Parser TxOutAnyEra +parseTxOutAnyEra :: Parser TxOutAnyEra' parseTxOutAnyEra = do addr <- parseAddressAny spaces -- Accept the old style of separating the address and value in a -- transaction output: option () (char '+' >> spaces) - TxOutAnyEra addr <$> parseValue + val <- parseValue + return $ TxOutAnyEra' addr val parseAddressAny :: Parser AddressAny parseAddressAny = do diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Parsers.hs b/cardano-cli/src/Cardano/CLI/Shelley/Parsers.hs index 1fe9e3e84a8..f12fa2d0c09 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Parsers.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Parsers.hs @@ -1730,14 +1730,31 @@ parseTxIx = toEnum <$> Atto.decimal pTxOut :: Parser TxOutAnyEra pTxOut = - Opt.option (readerFromParsecParser parseTxOutAnyEra) - ( Opt.long "tx-out" - <> Opt.metavar "TX-OUT" - -- TODO: Update the help text to describe the new syntax as well. - <> Opt.help "The transaction output as Address+Lovelace where Address is \ - \the Bech32-encoded address followed by the amount in \ - \Lovelace." - ) + toTxOutanyEra + <$> Opt.option (readerFromParsecParser parseTxOutAnyEra) + ( Opt.long "tx-out" + <> Opt.metavar "TX-OUT" + -- TODO: Update the help text to describe the new syntax as well. + <> Opt.help "The transaction output as Address+Lovelace where Address is \ + \the Bech32-encoded address followed by the amount in \ + \Lovelace." + ) + <*> optional pDatumHash + + +pDatumHash :: Parser Text +pDatumHash = + Opt.option (readerFromAttoParser parseHex) + ( Opt.long "datum-hash" + <> Opt.metavar "HASH" + <> Opt.help "Required datum hash for tx inputs intended \ + \to be utilizied by a Plutus script." + ) + +parseHex :: Atto.Parser Text +parseHex = + Text.decodeUtf8 <$> Atto.takeWhile1 Char.isHexDigit + pMultiAsset :: Parser Value pMultiAsset = diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Query.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Query.hs index 1968b8b194d..64bdcd7cbf0 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Query.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Query.hs @@ -199,7 +199,7 @@ runQueryTip (AnyConsensusModeParams cModeParams) network mOutFile = do case mOutFile of Just (OutputFile fpath) -> liftIO $ LBS.writeFile fpath output Nothing -> liftIO $ LBS.putStrLn output - + where tuple3Fst :: (a, b, c) -> a tuple3Fst (a, _, _) = a @@ -623,12 +623,12 @@ printUtxo shelleyBasedEra' txInOutTuple = , " " <> printableValue value ] ShelleyBasedEraAlonzo -> - let (TxIn (TxId txhash) (TxIx index), TxOut _ value _) = txInOutTuple + let (TxIn (TxId txhash) (TxIx index), TxOut _ value mDatum) = txInOutTuple in Text.putStrLn $ mconcat [ Text.decodeLatin1 (hashToBytesAsHex txhash) , textShowN 6 index - , " " <> printableValue value + , " " <> printableValue value <> " + " <> Text.pack (show mDatum) ] where textShowN :: Show a => Int -> a -> Text diff --git a/cardano-cli/src/Cardano/CLI/Shelley/Run/Transaction.hs b/cardano-cli/src/Cardano/CLI/Shelley/Run/Transaction.hs index 0442290b379..db25d5d3791 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Run/Transaction.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Run/Transaction.hs @@ -21,6 +21,7 @@ import qualified Data.ByteString.Lazy.Char8 as LBS import qualified Data.Map.Strict as Map import qualified Data.Set as Set import qualified Data.Text as Text +import qualified Data.Text.Encoding as Text import Data.Type.Equality (TestEquality (..)) import Control.Monad.Trans.Except.Extra (firstExceptT, handleIOExceptT, hoistEither, @@ -390,10 +391,29 @@ validateTxOuts era = mapM toTxOutInAnyEra where toTxOutInAnyEra :: TxOutAnyEra -> ExceptT ShelleyTxCmdError IO (TxOut era) - toTxOutInAnyEra (TxOutAnyEra addr val) = TxOut <$> toAddressInAnyEra addr - <*> toTxOutValueInAnyEra val - <*> pure TxOutDatumHashNone - -- TODO alonzo ^^ allow tx out data + toTxOutInAnyEra (TxOutAnyEra addr val mDatumHash) = + case scriptDataSupportedInEra era of + Nothing -> + TxOut <$> toAddressInAnyEra addr + <*> toTxOutValueInAnyEra val + <*> pure TxOutDatumHashNone + Just supported -> + case mDatumHash of + Nothing -> + TxOut <$> toAddressInAnyEra addr + <*> toTxOutValueInAnyEra val + <*> pure TxOutDatumHashNone + Just datHashText -> + case deserialiseFromRawBytesHex (AsHash AsScriptData) (Text.encodeUtf8 datHashText) of + Just sDataHash -> + TxOut <$> toAddressInAnyEra addr + <*> toTxOutValueInAnyEra val + <*> pure (TxOutDatumHash supported sDataHash) + Nothing -> + TxOut <$> toAddressInAnyEra addr + <*> toTxOutValueInAnyEra val + <*> pure TxOutDatumHashNone + toAddressInAnyEra :: AddressAny -> ExceptT ShelleyTxCmdError IO (AddressInEra era) toAddressInAnyEra addrAny = diff --git a/cardano-cli/src/Cardano/CLI/Types.hs b/cardano-cli/src/Cardano/CLI/Types.hs index c7942d93083..d8ce23ac1df 100644 --- a/cardano-cli/src/Cardano/CLI/Types.hs +++ b/cardano-cli/src/Cardano/CLI/Types.hs @@ -19,10 +19,12 @@ module Cardano.CLI.Types , ScriptDatumOrFile (..) , TransferDirection(..) , TxOutAnyEra (..) + , TxOutAnyEra' (..) , UpdateProposalFile (..) , VerificationKeyFile (..) , Stakes (..) , Params (..) + , toTxOutanyEra ) where import Cardano.Prelude @@ -193,6 +195,21 @@ data TransferDirection = TransferToReserves | TransferToTreasury -- values passed on the command line. It can be converted into the -- era-dependent 'TxOutValue' type. -- -data TxOutAnyEra = TxOutAnyEra AddressAny Value - -- TODO alonzo: ^^ add support for tx out data + + +toTxOutanyEra :: TxOutAnyEra' -> Maybe Text -> TxOutAnyEra +toTxOutanyEra (TxOutAnyEra' addr v) mHash = TxOutAnyEra addr v mHash + +data TxOutAnyEra = TxOutAnyEra + AddressAny + Value + -- Datum hash + (Maybe Text) + deriving (Eq, Show) + + +data TxOutAnyEra' = TxOutAnyEra' + AddressAny + Value + deriving (Eq, Show) diff --git a/scripts/plutus/example-txin-locking-plutus-script.sh b/scripts/plutus/example-txin-locking-plutus-script.sh new file mode 100755 index 00000000000..79707c4b1c7 --- /dev/null +++ b/scripts/plutus/example-txin-locking-plutus-script.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +set -e +# Unoffiical bash strict mode. +# See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ +set -u +set -o pipefail + +# cardano-cli address build --payment-script-file scripts/plutus/always-succeeds-txin.plutus --testnet-magic 42 +# Always succeeding PLUTUS SCRIPT ADDRESS - addr_test1wzpu87f5k52qvua9wn6gncg0m6hle4w86w5c4m9ryk6dxmsrw7evr + +#UTXOADDR=$(cardano-cli address build --testnet-magic 42 --payment-verification-key-file example/shelley/utxo-keys/utxo1.vkey) + +#TXIN=$(cardano-cli transaction txid --tx-body-file tx3.txbody)# + +#cardano-cli query protocol-parameters --testnet-magic 42 --out-file example/pparams.json + +# This script demonstrates how to lock a txin with a plutus script. NB: In this example any datum will succeed + +# Step 1: Create a tx ouput with a datum hash at the script address. In order for a tx ouput to be locked +# by a plutus script, it must have a datahash. +# We also need collateral tx inputs so we splut the utxo as well to accomodate this. + +plutusscriptaddr=$(cardano-cli address build --payment-script-file scripts/plutus/always-succeeds-txin.plutus --testnet-magic 42) + +utxovkey=example/shelley/utxo-keys/utxo1.vkey +utxoskey=example/shelley/utxo-keys/utxo1.skey + +utxoaddr=$(cardano-cli address build --testnet-magic 42 --payment-verification-key-file $utxovkey) + +utxo=$(cardano-cli query utxo --address $utxoaddr --cardano-mode --testnet-magic 42 --out-file utxo.json) + +txin=$(jq -r 'keys[]' utxo.json) + +cardano-cli transaction build-raw \ + --alonzo-era \ + --fee 0 \ + --tx-in $txin \ + --tx-out $plutusscriptaddr+500000000 --datum-hash 9e1199a988ba72ffd6e9c269cadb3b53b5f360ff99f112d9b2ee30c4d74ad88b \ + --tx-out $utxoaddr+500000000 \ + --out-file create-datum-output.body + +cardano-cli transaction sign \ + --tx-body-file create-datum-output.body \ + --testnet-magic 42 \ + --signing-key-file $utxoskey\ + --out-file create-datum-output.tx + +# Step 2 +# After "locking" our tx output at the script address, we can now can attempt to spend +# the "locked" tx output below. + +plutusutxo=$(cardano-cli query utxo --address $plutusscriptaddr --testnet-magic 42 --out-file plutusutxo.json) +plutusutxotxin=$(jq -r 'keys[]' plutusutxo.json) + +cardano-cli query protocol-parameters --testnet-magic 42 --out-file example/pparams.json + +cardano-cli transaction build-raw \ + --alonzo-era \ + --fee 0 \ + --tx-in $plutusutxotxin \ + --tx-in-collateral $txin \ + --tx-out $utxoaddr+500000000 \ + --txin-script-file ./scripts/plutus/always-succeeds-txin.plutus \ + --datum-value 42 \ + --protocol-params-file example/pparams.json\ + --redeemer-value 42 \ + --execution-units "(0,0)" \ + --out-file test-alonzo.body + +cardano-cli transaction sign \ + --tx-body-file test-alonzo.body \ + --testnet-magic 42 \ + --signing-key-file example/shelley/utxo-keys/utxo1.skey \ + --out-file alonzo.tx +# +# cardano-cli transaction submit --tx-file alonzo.tx --testnet-magic 42 +# +# +#> cabal exec cardano-cli -- query utxo --address addr_test1vrnzmw3tcq6llyh79tu7ym86p2gfhj47ap0wqcwkweqpnaq0r6yg4 --cardano-mode --testnet-magic 42 \ No newline at end of file