diff --git a/exe/wallet/http-bridge/Main.hs b/exe/wallet/http-bridge/Main.hs index bc4f9369b8a..6c74699a591 100644 --- a/exe/wallet/http-bridge/Main.hs +++ b/exe/wallet/http-bridge/Main.hs @@ -140,15 +140,9 @@ main = runCli $ cli $ mempty {------------------------------------------------------------------------------- Command - 'launch' - - cardano-wallet launch - [--network=STRING] - [(--port=INT | --random-port)] - [--node-port=INT] - [--state-dir=DIR] - [(--quiet | --verbose )] -------------------------------------------------------------------------------} +-- | Arguments for the 'launch' command data LaunchArgs = LaunchArgs { _network :: Either Local Network , _listen :: Listen @@ -157,7 +151,6 @@ data LaunchArgs = LaunchArgs , _verbosity :: Verbosity } --- | cardano-wallet launch cmdLaunch :: Mod CommandFields (IO ()) cmdLaunch = command "launch" $ info (helper <*> cmd) $ mempty @@ -210,13 +203,6 @@ cmdLaunch = command "launch" $ info (helper <*> cmd) $ mempty {------------------------------------------------------------------------------- Command - 'serve' - - cardano-wallet serve - [--network=STRING] - [(--port=INT | --random-port)] - [--node-port=INT] - [--database=FILE] - [(--quiet | --verbose )] -------------------------------------------------------------------------------} -- | Arguments for the 'serve' command @@ -228,7 +214,6 @@ data ServeArgs = ServeArgs , _verbosity :: Verbosity } --- | cardano-wallet serve cmdServe :: Mod CommandFields (IO ()) cmdServe = command "serve" $ info (helper <*> cmd) $ mempty diff --git a/exe/wallet/jormungandr/Main.hs b/exe/wallet/jormungandr/Main.hs index d5a162496cc..b8db8000f51 100644 --- a/exe/wallet/jormungandr/Main.hs +++ b/exe/wallet/jormungandr/Main.hs @@ -166,16 +166,9 @@ main = runCli $ cli $ mempty {------------------------------------------------------------------------------- Command - 'launch' - - cardano-wallet launch - [(--port=INT | --random-port)] - [--node-port=INT] - [--state-dir=DIR] - [(--quiet | --verbose )] - --genesis-block=FILE - --bft-leaders=FILE -------------------------------------------------------------------------------} +-- | Arguments for the 'launch' command data LaunchArgs = LaunchArgs { _listen :: Listen , _nodePort :: Port "Node" @@ -189,7 +182,6 @@ data JormungandrArgs = JormungandrArgs , _bftLeaders :: FilePath } --- | cardano-wallet launch cmdLaunch :: Mod CommandFields (IO ()) cmdLaunch = command "launch" $ info (helper <*> cmd) $ mempty @@ -267,14 +259,6 @@ parseBlock0H file = do {------------------------------------------------------------------------------- Command - 'serve' - - cardano-wallet serve - [--network=STRING] - [(--port=INT | --random-port)] - [--node-port=INT] - [--database=FILE] - [(--quiet | --verbose )] - --genesis-hash=STRING -------------------------------------------------------------------------------} -- | Arguments for the 'serve' command @@ -286,7 +270,6 @@ data ServeArgs = ServeArgs , _block0H :: Hash "Genesis" } --- | cardano-wallet serve cmdServe :: Mod CommandFields (IO ()) cmdServe = command "serve" $ info (helper <*> cmd) $ mempty diff --git a/lib/cli/cardano-wallet-cli.cabal b/lib/cli/cardano-wallet-cli.cabal index da824058c1e..5cb0fbf4b39 100644 --- a/lib/cli/cardano-wallet-cli.cabal +++ b/lib/cli/cardano-wallet-cli.cabal @@ -68,8 +68,12 @@ test-suite unit -Werror build-depends: base + , cardano-crypto , cardano-wallet-cli + , cardano-wallet-core , hspec + , memory + , optparse-applicative , QuickCheck , quickcheck-instances , text diff --git a/lib/cli/src/Cardano/CLI.hs b/lib/cli/src/Cardano/CLI.hs index 418c921ed13..98a338477a7 100644 --- a/lib/cli/src/Cardano/CLI.hs +++ b/lib/cli/src/Cardano/CLI.hs @@ -269,11 +269,8 @@ runCli = join . customExecParser preferences {------------------------------------------------------------------------------- Commands - 'mnemonic' - - cardano-wallet mnemonic generate [--size=INT] -------------------------------------------------------------------------------} --- | cardano-wallet mnemonic cmdMnemonic :: Mod CommandFields (IO ()) cmdMnemonic = command "mnemonic" $ info (helper <*> cmds) mempty where @@ -285,7 +282,6 @@ newtype MnemonicGenerateArgs = MnemonicGenerateArgs { _size :: MnemonicSize } --- | cardano-wallet mnemonic generate [--size=INT] cmdMnemonicGenerate :: Mod CommandFields (IO ()) cmdMnemonicGenerate = command "generate" $ info (helper <*> cmd) $ mempty <> progDesc "Generate English BIP-0039 compatible mnemonic words." @@ -303,16 +299,8 @@ cmdMnemonicGenerate = command "generate" $ info (helper <*> cmd) $ mempty {------------------------------------------------------------------------------- Commands - 'wallet' - - cardano-wallet wallet list [--port=INT] - cardano-wallet wallet create [--port=INT] [--address-pool-gap=INT] - cardano-wallet wallet get [--port=INT] - cardano-wallet wallet update name [--port=INT] STRING - cardano-wallet wallet update passphrase [--port=INT] - cardano-wallet wallet delete [--port=INT] -------------------------------------------------------------------------------} --- | cardano-wallet wallet cmdWallet :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -330,7 +318,6 @@ newtype WalletListArgs = WalletListArgs { _port :: Port "Wallet" } --- | cardano-wallet wallet list [--port=INT] cmdWalletList :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -348,7 +335,6 @@ data WalletCreateArgs = WalletCreateArgs , _gap :: AddressPoolGap } --- | cardano-wallet wallet create [--port=INT] [--address-pool-gap=INT] cmdWalletCreate :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -389,7 +375,6 @@ data WalletGetArgs = WalletGetArgs , _id :: WalletId } --- | cardano-wallet wallet get [--port=INT] cmdWalletGet :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -420,7 +405,6 @@ data WalletUpdateNameArgs = WalletUpdateNameArgs , _name :: WalletName } --- | cardano-wallet wallet update name [--port=INT] STRING cmdWalletUpdateName :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -442,7 +426,6 @@ data WalletUpdatePassphraseArgs = WalletUpdatePassphraseArgs , _id :: WalletId } --- | cardano-wallet wallet update passphrase [--port=INT] cmdWalletUpdatePassphrase :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -474,7 +457,6 @@ data WalletDeleteArgs = WalletDeleteArgs , _id :: WalletId } --- | cardano-wallet wallet delete [--port=INT] cmdWalletDelete :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -490,9 +472,6 @@ cmdWalletDelete = command "delete" $ info (helper <*> cmd) $ mempty {------------------------------------------------------------------------------- Commands - 'transaction' - - cardano-wallet transaction create [--port=INT] --payment=PAYMENT... - cardano-wallet transaction fees [--port=INT] --payment=PAYMENT... -------------------------------------------------------------------------------} -- | cardano-wallet transaction @@ -512,7 +491,6 @@ data TransactionCreateArgs t = TransactionCreateArgs , _payments :: NonEmpty (AddressAmount t) } --- | cardano-wallet wallet list [--port=INT] cmdTransactionCreate :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -558,11 +536,8 @@ cmdTransactionFees = command "fees" $ info (helper <*> cmd) $ mempty {------------------------------------------------------------------------------- Commands - 'address' - - cardano-wallet address list [--port=INT] [--state=STRING] -------------------------------------------------------------------------------} --- | cardano-wallet address cmdAddress :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -578,7 +553,6 @@ data AddressListArgs = AddressListArgs , _id :: WalletId } --- | cardano-wallet address list [--port=INT] [--state=STRING] cmdAddressList :: forall t. (DecodeAddress t, EncodeAddress t) => Mod CommandFields (IO ()) @@ -596,11 +570,8 @@ cmdAddressList = command "list" $ info (helper <*> cmd) $ mempty {------------------------------------------------------------------------------- Commands - 'version' - - cardano-wallet version -------------------------------------------------------------------------------} --- | cardano-wallet version cmdVersion :: Mod CommandFields (IO ()) cmdVersion = command "version" $ info cmd $ mempty <> progDesc "Show the program's version." @@ -612,8 +583,6 @@ cmdVersion = command "version" $ info cmd $ mempty {------------------------------------------------------------------------------- Commands - 'launch' - - cardano-wallet launch ... -------------------------------------------------------------------------------} -- | Execute 'launch' commands. This differs from the 'serve' command as it diff --git a/lib/cli/test/unit/Cardano/CLISpec.hs b/lib/cli/test/unit/Cardano/CLISpec.hs index 961d86b685c..358d426c7cc 100644 --- a/lib/cli/test/unit/Cardano/CLISpec.hs +++ b/lib/cli/test/unit/Cardano/CLISpec.hs @@ -14,15 +14,30 @@ import Cardano.CLI ( Iso8601Time (..) , MnemonicSize (..) , Port (..) + , cli + , cmdAddress + , cmdMnemonic + , cmdTransaction + , cmdWallet , hGetLine , hGetSensitiveLine ) +import Cardano.Crypto.Wallet + ( unXPub ) +import Cardano.Wallet.Primitive.AddressDerivation + ( KeyToAddress (..), getKey ) +import Cardano.Wallet.Primitive.Types + ( Address (..), DecodeAddress (..), EncodeAddress (..) ) import Control.Concurrent ( forkFinally ) import Control.Concurrent.MVar ( newEmptyMVar, putMVar, takeMVar ) import Control.Monad ( mapM_ ) +import Data.Bifunctor + ( bimap ) +import Data.ByteArray.Encoding + ( Base (Base16), convertFromBase, convertToBase ) import Data.Either ( isLeft, isRight ) import Data.Proxy @@ -31,6 +46,8 @@ import Data.Text ( Text ) import Data.Text.Class ( FromText (..), TextDecodingError (..), toText ) +import Options.Applicative + ( ParserResult (..), columns, execParserPure, prefs, renderFailure ) import System.IO ( Handle, IOMode (..), hClose, openFile ) import Test.Hspec @@ -40,6 +57,7 @@ import Test.QuickCheck , Large (..) , arbitraryBoundedEnum , checkCoverage + , counterexample , cover , genericShrink , property @@ -52,10 +70,205 @@ import Test.Text.Roundtrip ( textRoundtrip ) import qualified Data.Text as T +import qualified Data.Text.Encoding as T import qualified Data.Text.IO as TIO spec :: Spec spec = do + describe "Specification / Usage Overview" $ do + let parser = cli $ mempty + <> cmdMnemonic + <> cmdWallet @DummyTarget + <> cmdTransaction @DummyTarget + <> cmdAddress @DummyTarget + + let defaultPrefs = prefs (mempty <> columns 65) + + let expectationFailure = flip counterexample False + + let shouldShowUsage args expected = it (unwords args) $ + case execParserPure defaultPrefs parser args of + Success _ -> expectationFailure + "expected parser to show usage but it has succeeded" + CompletionInvoked _ -> expectationFailure + "expected parser to show usage but it offered completion" + Failure failure -> property $ + let (usage, _) = renderFailure failure mempty + in counterexample usage $ expected === lines usage + + ["--help"] `shouldShowUsage` + [ "The CLI is a proxy to the wallet server, which is required for" + , "most commands. Commands are turned into corresponding API calls," + , "and submitted to an up-and-running server. Some commands do not" + , "require an active server and can be run offline (e.g. 'mnemonic" + , "generate')." + , "" + , "Usage: COMMAND" + , " Cardano Wallet Command-Line Interface (CLI)" + , "" + , "Available options:" + , " -h,--help Show this help text" + , "" + , "Available commands:" + , " mnemonic " + , " wallet " + , " transaction " + , " address " + ] + + ["mnemonic", "--help"] `shouldShowUsage` + [ "Usage: mnemonic COMMAND" + , "" + , "Available options:" + , " -h,--help Show this help text" + , "" + , "Available commands:" + , " generate Generate English BIP-0039 compatible" + , " mnemonic words." + ] + + ["mnemonic", "generate", "--help"] `shouldShowUsage` + [ "Usage: mnemonic generate [--size INT]" + , " Generate English BIP-0039 compatible mnemonic words." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --size INT number of mnemonic words to" + , " generate. (default: 15)" + ] + + ["wallet", "--help"] `shouldShowUsage` + [ "Usage: wallet COMMAND" + , "" + , "Available options:" + , " -h,--help Show this help text" + , "" + , "Available commands:" + , " list List all known wallets." + , " create Create a new wallet using a sequential" + , " address scheme." + , " get Fetch the wallet with specified id." + , " update Update a wallet." + , " delete Deletes wallet with specified wallet" + , " id." + ] + + ["wallet", "list", "--help"] `shouldShowUsage` + [ "Usage: wallet list [--port INT]" + , " List all known wallets." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + ] + + ["wallet", "create", "--help"] `shouldShowUsage` + [ "Usage: wallet create [--port INT] STRING" + , " [--address-pool-gap INT]" + , " Create a new wallet using a sequential address scheme." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + , " --address-pool-gap INT number of unused consecutive addresses" + , " to keep track of. (default: 20)" + ] + + ["wallet", "get", "--help"] `shouldShowUsage` + [ "Usage: wallet get [--port INT] WALLET_ID" + , " Fetch the wallet with specified id." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + ] + + ["wallet", "update", "--help"] `shouldShowUsage` + [ "Usage: wallet update COMMAND" + , " Update a wallet." + , "" + , "Available options:" + , " -h,--help Show this help text" + , "" + , "Available commands:" + , " name Update a wallet's name." + , " passphrase Update a wallet's passphrase." + ] + + ["wallet", "delete", "--help"] `shouldShowUsage` + [ "Usage: wallet delete [--port INT] WALLET_ID" + , " Deletes wallet with specified wallet id." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + ] + + ["transaction", "--help"] `shouldShowUsage` + [ "Usage: transaction COMMAND" + , "" + , "Available options:" + , " -h,--help Show this help text" + , "" + , "Available commands:" + , " create Create and submit a new transaction." + , " fees Estimate fees for a transaction." + ] + + ["transaction", "create", "--help"] `shouldShowUsage` + [ "Usage: transaction create [--port INT] WALLET_ID" + , " --payment PAYMENT" + , " Create and submit a new transaction." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + , " --payment PAYMENT address to send to and amount to send" + , " separated by @, e.g." + , " '@
'" + ] + + ["transaction", "fees", "--help"] `shouldShowUsage` + [ "Usage: transaction fees [--port INT] WALLET_ID --payment PAYMENT" + , " Estimate fees for a transaction." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + , " --payment PAYMENT address to send to and amount to send" + , " separated by @, e.g." + , " '@
'" + ] + + ["address", "--help"] `shouldShowUsage` + [ "Usage: address COMMAND" + , "" + , "Available options:" + , " -h,--help Show this help text" + , "" + , "Available commands:" + , " list List all known addresses of a given" + , " wallet." + ] + + ["address", "list", "--help"] `shouldShowUsage` + [ "Usage: address list [--port INT] [--state STRING] WALLET_ID" + , " List all known addresses of a given wallet." + , "" + , "Available options:" + , " -h,--help Show this help text" + , " --port INT port used for serving the wallet" + , " API. (default: 8090)" + , " --state STRING only addresses with the given state:" + , " either 'used' or 'unused'." + ] + describe "Can perform roundtrip textual encoding & decoding" $ do textRoundtrip $ Proxy @Iso8601Time textRoundtrip $ Proxy @(Port "test") @@ -367,3 +580,23 @@ ensureIso8601TimesEquivalent t1 t2 = it title $ property $ title = mempty <> "Equivalent ISO 8601 times are decoded equivalently: " <> show (t1, t2) + +{------------------------------------------------------------------------------- + Dummy Target +-------------------------------------------------------------------------------} + +data DummyTarget + +instance KeyToAddress DummyTarget where + keyToAddress = Address . unXPub . getKey + +instance EncodeAddress DummyTarget where + encodeAddress _ = T.decodeUtf8 . convertToBase Base16 . unAddress + +instance DecodeAddress DummyTarget where + decodeAddress _ = bimap decodingError Address + . convertFromBase Base16 + . T.encodeUtf8 + where + decodingError _ = TextDecodingError + "Unable to decode Address: expected Base16 encoding" diff --git a/nix/.stack.nix/cardano-wallet-cli.nix b/nix/.stack.nix/cardano-wallet-cli.nix index d8b8d978f5c..306e43facc9 100644 --- a/nix/.stack.nix/cardano-wallet-cli.nix +++ b/nix/.stack.nix/cardano-wallet-cli.nix @@ -42,8 +42,12 @@ "unit" = { depends = [ (hsPkgs.base) + (hsPkgs.cardano-crypto) (hsPkgs.cardano-wallet-cli) + (hsPkgs.cardano-wallet-core) (hsPkgs.hspec) + (hsPkgs.memory) + (hsPkgs.optparse-applicative) (hsPkgs.QuickCheck) (hsPkgs.quickcheck-instances) (hsPkgs.text)