diff --git a/ChangeLog.md b/ChangeLog.md index c06bd4cf..54ee54dc 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,10 @@ ## Unreleased - Support node version 7 and protocol version 7. + - Display account balance "at disposal". (Note, this will (incorrectly) show as 0 if connecting to + an older version of the node.) + - List the cooldowns on an account, and their expiration times. +- Improved checks when configuring a validator or delegator. - Fix the display of the expected expiry of pending changes to an account's stake, so that they correctly account for the change taking place at a payday. diff --git a/src/Concordium/Client/Output.hs b/src/Concordium/Client/Output.hs index f6e918b1..5c4bc481 100644 --- a/src/Concordium/Client/Output.hs +++ b/src/Concordium/Client/Output.hs @@ -191,10 +191,13 @@ showRevealedAttributes as = printAccountInfo :: NamedAddress -> Types.AccountInfo -> Verbose -> Bool -> Maybe (ElgamalSecretKey, GlobalContext) -> Printer printAccountInfo addr a verbose showEncrypted mEncKey = do + let balance = showCcd $ Types.aiAccountAmount a + let rjustCcd amt = let t = showCcd amt in replicate (length balance - length t) ' ' ++ t tell ( [ [i|Local names: #{showNameList $ naNames addr}|], [i|Address: #{naAddr addr}|], - [i|Balance: #{showCcd $ Types.aiAccountAmount a}|] + [i|Balance: #{balance}|], + [i| - At disposal: #{rjustCcd (Types.aiAccountAvailableAmount a)}|] ] ++ case Types.releaseTotal $ Types.aiAccountReleaseSchedule a of 0 -> [] @@ -292,6 +295,11 @@ printAccountInfo addr a verbose showEncrypted mEncKey = do ] tell [[i| - Restake earnings: #{showYesNo asiStakeEarnings}|]] + unless (null (Types.aiAccountCooldowns a)) $ do + tell ["Inactive stake in cooldown:"] + forM_ (Types.aiAccountCooldowns a) $ \Types.Cooldown{..} -> + tell [[i| #{showCcd cooldownAmount} available after #{showTimeFormatted (Time.timestampToUTCTime cooldownTimestamp)}|]] + tell [""] if Map.null $ Types.aiAccountCredentials a diff --git a/src/Concordium/Client/Runner.hs b/src/Concordium/Client/Runner.hs index fb0135ce..ab9c0b47 100644 --- a/src/Concordium/Client/Runner.hs +++ b/src/Concordium/Client/Runner.hs @@ -3027,37 +3027,56 @@ processBakerConfigureCmd baseCfgDir verbose backend txOpts isBakerConfigure cbCa let outFile = toOutFile txOpts (bakerKeys, txCfg, pl) <- transactionForBakerConfigure (ioConfirm intOpts) withClient backend $ do - when isBakerConfigure $ warnAboutMissingAddBakerParameters txCfg - mapM_ (warnAboutBadCapital txCfg) cbCapital + let senderAddr = naAddr . esdAddress . tcEncryptedSigningData $ txCfg + accountInfo <- getAccountInfoOrDie (Types.AccAddress senderAddr) Best + when isBakerConfigure $ warnAboutMissingAddBakerParameters accountInfo + mapM_ (warnAboutBadCapital txCfg accountInfo) cbCapital result <- signAndProcessTransaction verbose txCfg pl intOpts outFile backend events <- eventsFromTransactionResult result mapM_ (tryPrintKeyUpdateEventToOutputFile bakerKeys) events where - warnAboutMissingAddBakerParameters txCfg = do - let allPresent = case (cbOpenForDelegation, metadataURL, cbTransactionFeeCommission, cbBakingRewardCommission, cbFinalizationRewardCommission, cbRestakeEarnings, cbCapital, inputKeysFile) of - (Just _, Just _, Just _, Just _, Just _, Just _, Just _, Just _) -> True - _ -> False - when (not allPresent) $ do - let senderAddr = naAddr . esdAddress . tcEncryptedSigningData $ txCfg - Types.AccountInfo{..} <- getAccountInfoOrDie (Types.AccAddress senderAddr) Best - case aiStakingInfo of - Types.AccountStakingBaker{} -> return () - _ -> do - logWarn $ - [ init - ( "To add a validator, more options are necessary. The following are missing " - ++ (if isNothing cbCapital then "\n--stake," else "") - ++ (if isNothing cbOpenForDelegation then "\n--open-delegation-for," else "") - ++ (if isNothing inputKeysFile then "\n--keys-in," else "") - ++ (if isNothing metadataURL then "\n--validator-url," else "") - ++ (if isNothing cbTransactionFeeCommission then "\n--delegation-transaction-fee-commission," else "") - ++ (if isNothing cbBakingRewardCommission then "\n--delegation-block-reward-commission," else "") - ++ (if isNothing cbFinalizationRewardCommission then "\n--delegation-finalization-commission," else "") - ) - ++ (if isNothing cbRestakeEarnings then ". \nExactly one of the options --restake and --no-restake must be present" else "") - ] - confirmed <- askConfirmation $ Just "This transaction will most likely be rejected by the chain, do you wish to send it anyway" - unless confirmed exitTransactionCancelled + warnAboutMissingAddBakerParameters Types.AccountInfo{..} = do + let baseRequirements = + isJust $ + cbOpenForDelegation + >> metadataURL + >> cbTransactionFeeCommission + >> cbBakingRewardCommission + >> cbFinalizationRewardCommission + >> inputKeysFile + let noDelegationRequirements = isJust $ cbRestakeEarnings >> cbCapital + case aiStakingInfo of + Types.AccountStakingNone | not (baseRequirements && noDelegationRequirements) -> do + logWarn $ + [ init + ( "To add a validator, more options are necessary. The following are missing " + ++ (if isNothing cbCapital then "\n--stake," else "") + ++ (if isNothing cbOpenForDelegation then "\n--open-delegation-for," else "") + ++ (if isNothing inputKeysFile then "\n--keys-in," else "") + ++ (if isNothing metadataURL then "\n--validator-url," else "") + ++ (if isNothing cbTransactionFeeCommission then "\n--delegation-transaction-fee-commission," else "") + ++ (if isNothing cbBakingRewardCommission then "\n--delegation-block-reward-commission," else "") + ++ (if isNothing cbFinalizationRewardCommission then "\n--delegation-finalization-commission," else "") + ) + ++ (if isNothing cbRestakeEarnings then ". \nExactly one of the options --restake and --no-restake must be present" else "") + ] + confirmed <- askConfirmation $ Just "This transaction will most likely be rejected by the chain, do you wish to send it anyway" + unless confirmed exitTransactionCancelled + Types.AccountStakingDelegated{} | not baseRequirements -> do + logWarn $ + [ init + ( "To switch from delegating to validating, more options are necessary. The following are missing " + ++ (if isNothing cbOpenForDelegation then "\n--open-delegation-for," else "") + ++ (if isNothing inputKeysFile then "\n--keys-in," else "") + ++ (if isNothing metadataURL then "\n--validator-url," else "") + ++ (if isNothing cbTransactionFeeCommission then "\n--delegation-transaction-fee-commission," else "") + ++ (if isNothing cbBakingRewardCommission then "\n--delegation-block-reward-commission," else "") + ++ (if isNothing cbFinalizationRewardCommission then "\n--delegation-finalization-commission," else "") + ) + ] + confirmed <- askConfirmation $ Just "This transaction will most likely be rejected by the chain, do you wish to send it anyway" + unless confirmed exitTransactionCancelled + _ -> return () askUntilEqual credentials = do pwd <- askPassword "Enter password for encryption of validator credentials (leave blank for no encryption): " case Password.getPassword pwd of @@ -3109,9 +3128,7 @@ processBakerConfigureCmd baseCfgDir verbose backend txOpts isBakerConfigure cbCa printToOutputFileIfJust bakerKeys ebkuBakerId tryPrintKeyUpdateEventToOutputFile _ _ = return () - warnAboutBadCapital txCfg capital = do - let senderAddr = naAddr . esdAddress . tcEncryptedSigningData $ txCfg - Types.AccountInfo{..} <- getAccountInfoOrDie (Types.AccAddress senderAddr) Best + warnAboutBadCapital txCfg Types.AccountInfo{..} capital = do warnIfCapitalIsSmall capital cannotAfford <- warnIfCannotAfford txCfg capital aiAccountAmount case aiStakingInfo of @@ -3889,7 +3906,10 @@ processDelegatorConfigureCmd baseCfgDir verbose backend txOpts cdCapital cdResta (txCfg, pl) <- transactionForDelegatorConfigure (ioConfirm intOpts) withClient backend $ do warnInOldProtocol - mapM_ (warnAboutBadCapital txCfg) cdCapital + let senderAddr = naAddr . esdAddress . tcEncryptedSigningData $ txCfg + accountInfo <- getAccountInfoOrDie (Types.AccAddress senderAddr) Best + warnAboutMissingAddDelegatorParameters accountInfo + mapM_ (warnAboutBadCapital txCfg accountInfo) cdCapital result <- signAndProcessTransaction verbose txCfg pl intOpts outFile backend warnAboutFailedResult result where @@ -3899,6 +3919,29 @@ processDelegatorConfigureCmd baseCfgDir verbose backend txOpts cdCapital cdResta logWarn [[i|Delegation is not supported in protocol versions < 4.|]] confirmed <- askConfirmation $ Just "This transaction will most likely be rejected by the chain, do you wish to send it anyway" unless confirmed exitTransactionCancelled + warnAboutMissingAddDelegatorParameters Types.AccountInfo{..} = do + -- If the account is not already a delegator or baker, we should warn if + -- any of cdCapital, cdRestakeEarnings or cdDelegationTarget is missing. + -- Moreover, if the account is a baker, we should warn if cdDelegation target is missing. + case aiStakingInfo of + Types.AccountStakingBaker{} + | isNothing cdDelegationTarget -> do + logWarn ["To switch from validating to delegating, the --target option must be specified."] + confirmed <- askConfirmation $ Just "This transaction will most likely be rejected by the chain, do you wish to send it anyway" + unless confirmed exitTransactionCancelled + Types.AccountStakingNone + | isNothing $ cdCapital >> cdDelegationTarget >> cdRestakeEarnings -> do + logWarn + [ init + ( "To add a delegator, more options are necessary. The following are missing " + ++ (if isNothing cdCapital then "\n--stake," else "") + ++ (if isNothing cdDelegationTarget then "\n--target," else "") + ++ (if isNothing cdRestakeEarnings then "\neither --restake or --no-restake," else "") + ) + ] + confirmed <- askConfirmation $ Just "This transaction will most likely be rejected by the chain, do you wish to send it anyway" + unless confirmed exitTransactionCancelled + _ -> return () warnAboutPoolStatus capital alreadyDelegatedToBakerPool alreadyBakerId = do case cdDelegationTarget of Nothing -> return () @@ -3918,9 +3961,7 @@ processDelegatorConfigureCmd baseCfgDir verbose backend txOpts cdCapital cdResta logWarn [[i|Staked amount (#{showCcd capital}) plus the stake already delegated the pool is larger than the maximum allowed delegated stake).|]] confirmed <- askConfirmation $ Just "This transaction will most likely be rejected by the chain, do you wish to send it anyway" unless confirmed exitTransactionCancelled - warnAboutBadCapital txCfg capital = do - let senderAddr = naAddr . esdAddress . tcEncryptedSigningData $ txCfg - Types.AccountInfo{..} <- getAccountInfoOrDie (Types.AccAddress senderAddr) Best + warnAboutBadCapital txCfg Types.AccountInfo{..} capital = do warnIfCannotAfford txCfg capital aiAccountAmount (alreadyDelegatedToBakerPool, alreadyBakerId) <- case aiStakingInfo of Types.AccountStakingDelegated{..} -> do @@ -3929,6 +3970,9 @@ processDelegatorConfigureCmd baseCfgDir verbose backend txOpts cdCapital cdResta Types.DelegatePassive -> return Nothing Types.DelegateToBaker bid -> return $ Just bid return (asiStakedAmount, mbid) + Types.AccountStakingBaker{..} -> do + liftIO $ warnIfCapitalIsLowered capital asiStakedAmount + return (0, Nothing) _ -> return (0, Nothing) warnAboutPoolStatus capital alreadyDelegatedToBakerPool alreadyBakerId diff --git a/test/SimpleClientTests/AccountSpec.hs b/test/SimpleClientTests/AccountSpec.hs index 096e5fff..0cdab504 100644 --- a/test/SimpleClientTests/AccountSpec.hs +++ b/test/SimpleClientTests/AccountSpec.hs @@ -107,7 +107,13 @@ exampleDelegatorStakingInfo pc = -- The credentials will be given indices 0, 1, .. exampleAccountInfoResult :: AccountStakingInfo -> [IDTypes.RawAccountCredential] -> AccountInfo -exampleAccountInfoResult staking cs = +exampleAccountInfoResult = exampleAccountInfoResultWithCooldowns [] + +exampleCooldowns :: [Cooldown] +exampleCooldowns = [Cooldown 3600000 200 StatusCooldown, Cooldown 7200000 400 StatusPreCooldown, Cooldown 9000000 500 StatusPrePreCooldown] + +exampleAccountInfoResultWithCooldowns :: [Cooldown] -> AccountStakingInfo -> [IDTypes.RawAccountCredential] -> AccountInfo +exampleAccountInfoResultWithCooldowns cooldowns staking cs = AccountInfo { aiAccountAmount = Types.Amount 1, aiAccountNonce = Types.Nonce 2, @@ -125,7 +131,7 @@ exampleAccountInfoResult staking cs = aiAccountEncryptionKey = dummyEncryptionPublicKey, aiAccountIndex = 27, aiAccountAddress = exampleAddress1, - aiAccountCooldowns = [], + aiAccountCooldowns = cooldowns, aiAccountAvailableAmount = Types.Amount 1 } @@ -231,6 +237,7 @@ printAccountInfoSpec = describe "printAccountInfo" $ do `shouldBe` [ "Local names: 'example'", "Address: 2zR4h351M1bqhrL9UywsbHrP3ucA1xY3TBTFRuTsRout8JnLD6", "Balance: 0.000001 CCD", + " - At disposal: 0.000001 CCD", "Nonce: 2", "Encryption public key: a820662531d0aac70b3a80dd8a249aa692436097d06da005aec7c56aad17997ec8331d1e4050fd8dced2b92f06277bd5aae71cf315a6d70c849508f6361ac6d51c2168305dd1604c4c6448da4499b2f14afb94fff0f42b79a68ed7ba206301f4", "", @@ -243,6 +250,7 @@ printAccountInfoSpec = describe "printAccountInfo" $ do `shouldBe` [ "Local names: 'example'", "Address: 2zR4h351M1bqhrL9UywsbHrP3ucA1xY3TBTFRuTsRout8JnLD6", "Balance: 0.000001 CCD", + " - At disposal: 0.000001 CCD", "Nonce: 2", "Encryption public key: a820662531d0aac70b3a80dd8a249aa692436097d06da005aec7c56aad17997ec8331d1e4050fd8dced2b92f06277bd5aae71cf315a6d70c849508f6361ac6d51c2168305dd1604c4c6448da4499b2f14afb94fff0f42b79a68ed7ba206301f4", "", @@ -257,6 +265,23 @@ printAccountInfoSpec = describe "printAccountInfo" $ do `shouldBe` [ "Local names: 'example'", "Address: 2zR4h351M1bqhrL9UywsbHrP3ucA1xY3TBTFRuTsRout8JnLD6", "Balance: 0.000001 CCD", + " - At disposal: 0.000001 CCD", + "Nonce: 2", + "Encryption public key: a820662531d0aac70b3a80dd8a249aa692436097d06da005aec7c56aad17997ec8331d1e4050fd8dced2b92f06277bd5aae71cf315a6d70c849508f6361ac6d51c2168305dd1604c4c6448da4499b2f14afb94fff0f42b79a68ed7ba206301f4", + "", + "Delegating stake: yes", + "Delegation target: Passive delegation", + " - Staked amount: 0.000100 CCD", + " - Restake earnings: no", + "", + "Credentials: none" + ] + specify "with delegator & cooldown" $ + p exampleNamedAddress (exampleAccountInfoResultWithCooldowns exampleCooldowns (exampleDelegatorStakingInfo NoChange) []) + `shouldBe` [ "Local names: 'example'", + "Address: 2zR4h351M1bqhrL9UywsbHrP3ucA1xY3TBTFRuTsRout8JnLD6", + "Balance: 0.000001 CCD", + " - At disposal: 0.000001 CCD", "Nonce: 2", "Encryption public key: a820662531d0aac70b3a80dd8a249aa692436097d06da005aec7c56aad17997ec8331d1e4050fd8dced2b92f06277bd5aae71cf315a6d70c849508f6361ac6d51c2168305dd1604c4c6448da4499b2f14afb94fff0f42b79a68ed7ba206301f4", "", @@ -264,6 +289,10 @@ printAccountInfoSpec = describe "printAccountInfo" $ do "Delegation target: Passive delegation", " - Staked amount: 0.000100 CCD", " - Restake earnings: no", + "Inactive stake in cooldown:", + " 0.000200 CCD available after Thu, 1 Jan 1970 01:00:00 UTC", + " 0.000400 CCD available after Thu, 1 Jan 1970 02:00:00 UTC", + " 0.000500 CCD available after Thu, 1 Jan 1970 02:30:00 UTC", "", "Credentials: none" ] @@ -285,6 +314,7 @@ printAccountInfoSpec = describe "printAccountInfo" $ do `shouldBe` [ "Local names: 'example'", "Address: 2zR4h351M1bqhrL9UywsbHrP3ucA1xY3TBTFRuTsRout8JnLD6", "Balance: 0.000001 CCD", + " - At disposal: 0.000001 CCD", "Release schedule: total 0.000100 CCD", " Tue, 3 Nov 2020 15:28:22 UTC: 0.000033 CCD scheduled by the transactions: f26a45adbb7d5cbefd9430d1eac665bd225fb3d8e04efb288d99a0347f0b8868, b041315fe35a8bdf836647037c24c8e87402547c82aea568c66ee18aa3091326.", " Tue, 3 Nov 2020 15:29:02 UTC: 0.000033 CCD scheduled by the transactions: f26a45adbb7d5cbefd9430d1eac665bd225fb3d8e04efb288d99a0347f0b8868.", @@ -301,6 +331,7 @@ printAccountInfoSpec = describe "printAccountInfo" $ do `shouldBe` [ "Local names: 'example'", "Address: 2zR4h351M1bqhrL9UywsbHrP3ucA1xY3TBTFRuTsRout8JnLD6", "Balance: 0.000001 CCD", + " - At disposal: 0.000001 CCD", "Nonce: 2", "Encryption public key: a820662531d0aac70b3a80dd8a249aa692436097d06da005aec7c56aad17997ec8331d1e4050fd8dced2b92f06277bd5aae71cf315a6d70c849508f6361ac6d51c2168305dd1604c4c6448da4499b2f14afb94fff0f42b79a68ed7ba206301f4", "", @@ -325,6 +356,7 @@ printAccountInfoSpec = describe "printAccountInfo" $ do `shouldBe` [ "Local names: 'example'", "Address: 2zR4h351M1bqhrL9UywsbHrP3ucA1xY3TBTFRuTsRout8JnLD6", "Balance: 0.000001 CCD", + " - At disposal: 0.000001 CCD", "Nonce: 2", "Encryption public key: a820662531d0aac70b3a80dd8a249aa692436097d06da005aec7c56aad17997ec8331d1e4050fd8dced2b92f06277bd5aae71cf315a6d70c849508f6361ac6d51c2168305dd1604c4c6448da4499b2f14afb94fff0f42b79a68ed7ba206301f4", "", @@ -347,6 +379,7 @@ printAccountInfoSpec = describe "printAccountInfo" $ do `shouldBe` [ "Local names: 'example'", "Address: 2zR4h351M1bqhrL9UywsbHrP3ucA1xY3TBTFRuTsRout8JnLD6", "Balance: 0.000001 CCD", + " - At disposal: 0.000001 CCD", "Nonce: 2", "Encryption public key: a820662531d0aac70b3a80dd8a249aa692436097d06da005aec7c56aad17997ec8331d1e4050fd8dced2b92f06277bd5aae71cf315a6d70c849508f6361ac6d51c2168305dd1604c4c6448da4499b2f14afb94fff0f42b79a68ed7ba206301f4", "", @@ -378,6 +411,7 @@ printAccountInfoSpec = describe "printAccountInfo" $ do `shouldBe` [ "Local names: 'example'", "Address: 2zR4h351M1bqhrL9UywsbHrP3ucA1xY3TBTFRuTsRout8JnLD6", "Balance: 0.000001 CCD", + " - At disposal: 0.000001 CCD", "Nonce: 2", "Encryption public key: a820662531d0aac70b3a80dd8a249aa692436097d06da005aec7c56aad17997ec8331d1e4050fd8dced2b92f06277bd5aae71cf315a6d70c849508f6361ac6d51c2168305dd1604c4c6448da4499b2f14afb94fff0f42b79a68ed7ba206301f4", "", @@ -401,6 +435,7 @@ printAccountInfoSpec = describe "printAccountInfo" $ do `shouldBe` [ "Local names: 'example'", "Address: 2zR4h351M1bqhrL9UywsbHrP3ucA1xY3TBTFRuTsRout8JnLD6", "Balance: 0.000001 CCD", + " - At disposal: 0.000001 CCD", "Nonce: 2", "Encryption public key: a820662531d0aac70b3a80dd8a249aa692436097d06da005aec7c56aad17997ec8331d1e4050fd8dced2b92f06277bd5aae71cf315a6d70c849508f6361ac6d51c2168305dd1604c4c6448da4499b2f14afb94fff0f42b79a68ed7ba206301f4", "",