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..f9361cf5730 100644 --- a/cardano-cli/src/Cardano/CLI/Mary/TxOutParser.hs +++ b/cardano-cli/src/Cardano/CLI/Mary/TxOutParser.hs @@ -7,25 +7,25 @@ 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 + TxOutAnyEra' addr <$> parseValue 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 8139b01995d..a5826cb8713 100644 --- a/cardano-cli/src/Cardano/CLI/Shelley/Parsers.hs +++ b/cardano-cli/src/Cardano/CLI/Shelley/Parsers.hs @@ -1746,14 +1746,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 alonzo: 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..828814524fa 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, @@ -47,10 +48,10 @@ import qualified Ouroboros.Network.Protocol.LocalTxSubmission.Client as Net.Tx import Cardano.CLI.Environment (EnvSocketError, readEnvSocketPath, renderEnvSocketError) import Cardano.CLI.Run.Friendly (friendlyTxBodyBS) import Cardano.CLI.Shelley.Key (InputDecodeError, readSigningKeyFileAnyOf) -import Cardano.CLI.Shelley.Script import Cardano.CLI.Shelley.Parsers import Cardano.CLI.Shelley.Run.Genesis (ShelleyGenesisCmdError (..), readShelleyGenesis, renderShelleyGenesisCmdError) +import Cardano.CLI.Shelley.Script import Cardano.CLI.Types import qualified System.IO as IO @@ -92,6 +93,7 @@ data ShelleyTxCmdError | ShelleyTxCmdGenesisCmdError !ShelleyGenesisCmdError | ShelleyTxCmdPolicyIdsMissing [PolicyId] | ShelleyTxCmdPolicyIdsExcess [PolicyId] + | ShelleyTxCmdDataHashSerializationError Text deriving Show data SomeTxBodyError where @@ -204,6 +206,8 @@ renderShelleyTxCmdError err = "A script provided to witness minting does not correspond to the policy \ \id of any asset specified in the \"--mint\" field. The script hash is: " <> Text.intercalate ", " (map serialiseToRawBytesHexText policyids) + ShelleyTxCmdDataHashSerializationError dHash -> + "Error deserializing datum hash: " <> dHash renderEra :: AnyCardanoEra -> Text renderEra (AnyCardanoEra ByronEra) = "Byron" @@ -390,10 +394,26 @@ 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 -> + left $ ShelleyTxCmdDataHashSerializationError datHashText 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..17d39f8e025 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) = TxOutAnyEra addr v + +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..bbdcec0b0f1 --- /dev/null +++ b/scripts/plutus/example-txin-locking-plutus-script.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash + +set -e +# Unoffiical bash strict mode. +# See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ +set -u +set -o pipefail + +# This script demonstrates how to lock a tx output with a plutus script. +# NB: In this example the Datum must be 42! + +# 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 split the utxo +# in order 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 + +# SUBMIT +cardano-cli transaction submit --tx-file create-datum-output.tx --testnet-magic 42 + +echo "Pausing for 5 seconds..." +sleep 5 + +# Step 2 +# After "locking" the tx output at the script address, we can now can attempt to spend +# the "locked" tx output below. + +cardano-cli query utxo --address $plutusscriptaddr --testnet-magic 42 --out-file plutusutxo.json +plutusutxotxin=$(jq -r 'keys[]' plutusutxo.json) + +cardano-cli query utxo --address $utxoaddr --cardano-mode --testnet-magic 42 --out-file utxo.json +txinCollateral=$(jq -r 'keys[]' utxo.json) + +echo "Plutus TxIn" +echo $plutusutxotxin + +echo "Collateral TxIn" +echo $txinCollateral + +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 $txinCollateral \ + --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 + +# SUBMIT alonzo.tx +echo "Manually submit the tx with:" +echo "cardano-cli transaction submit --tx-file alonzo.tx --testnet-magic 42"