split wallet staking commands (#6168)

* split wallet staking commands

* elide real home

* unit->UNIT for usage

* unit->UNIT, don't try to run SUBCOMMANDS: ;)

* more fixup

* fixups

* actually check

* shellcheck

* preserve #6158 after rebase

* fixup

* test

* too hard

* remove test
This commit is contained in:
Rob Walker 2019-09-29 21:18:15 -07:00 committed by GitHub
parent e5a7d08966
commit 4f4618441c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1457 additions and 805 deletions

34
book/build-cli-usage.sh Executable file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"
usage=$(cargo -q run -p solana-cli -- -C ~/.foo --help | sed 's|'"$HOME"'|~|g')
out=${1:-src/api-reference/cli.md}
cat src/api-reference/.cli.md > "$out"
section() {
declare mark=${2:-"###"}
declare section=$1
read -r name rest <<<"$section"
printf '%s %s
' "$mark" "$name"
printf '```text
%s
```
' "$section"
}
section "$usage" >> "$out"
in_subcommands=0
while read -r subcommand rest; do
[[ $subcommand == "SUBCOMMANDS:" ]] && in_subcommands=1 && continue
if ((in_subcommands)); then
section "$(cargo -q run -p solana-cli -- help "$subcommand" | sed 's|'"$HOME"'|~|g')" "####" >> "$out"
fi
done <<<"$usage">>"$out"

View File

@ -0,0 +1,177 @@
# solana CLI
The [solana-cli crate](https://crates.io/crates/solana-cli) provides a command-line interface tool for Solana
## Examples
### Get Pubkey
```bash
// Command
$ solana address
// Return
<PUBKEY>
```
### Airdrop SOL/Lamports
```bash
// Command
$ solana airdrop 2
// Return
"2.00000000 SOL"
// Command
$ solana airdrop 123 --lamports
// Return
"123 lamports"
```
### Get Balance
```bash
// Command
$ solana balance
// Return
"3.00050001 SOL"
```
### Confirm Transaction
```bash
// Command
$ solana confirm <TX_SIGNATURE>
// Return
"Confirmed" / "Not found" / "Transaction failed with error <ERR>"
```
### Deploy program
```bash
// Command
$ solana deploy <PATH>
// Return
<PROGRAM_ID>
```
### Unconditional Immediate Transfer
```bash
// Command
$ solana pay <PUBKEY> 123
// Return
<TX_SIGNATURE>
```
### Post-Dated Transfer
```bash
// Command
$ solana pay <PUBKEY> 123 \
--after 2018-12-24T23:59:00 --require-timestamp-from <PUBKEY>
// Return
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
```
_`require-timestamp-from` is optional. If not provided, the transaction will expect a timestamp signed by this wallet's private key_
### Authorized Transfer
A third party must send a signature to unlock the lamports.
```bash
// Command
$ solana pay <PUBKEY> 123 \
--require-signature-from <PUBKEY>
// Return
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
```
### Post-Dated and Authorized Transfer
```bash
// Command
$ solana pay <PUBKEY> 123 \
--after 2018-12-24T23:59 --require-timestamp-from <PUBKEY> \
--require-signature-from <PUBKEY>
// Return
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
```
### Multiple Witnesses
```bash
// Command
$ solana pay <PUBKEY> 123 \
--require-signature-from <PUBKEY> \
--require-signature-from <PUBKEY>
// Return
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
```
### Cancelable Transfer
```bash
// Command
$ solana pay <PUBKEY> 123 \
--require-signature-from <PUBKEY> \
--cancelable
// Return
{signature: <TX_SIGNATURE>, processId: <PROCESS_ID>}
```
### Cancel Transfer
```bash
// Command
$ solana cancel <PROCESS_ID>
// Return
<TX_SIGNATURE>
```
### Send Signature
```bash
// Command
$ solana send-signature <PUBKEY> <PROCESS_ID>
// Return
<TX_SIGNATURE>
```
### Indicate Elapsed Time
Use the current system time:
```bash
// Command
$ solana send-timestamp <PUBKEY> <PROCESS_ID>
// Return
<TX_SIGNATURE>
```
Or specify some other arbitrary timestamp:
```bash
// Command
$ solana send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
// Return
<TX_SIGNATURE>
```
## Usage

View File

@ -175,12 +175,13 @@ $ solana send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
```
## Usage
### solana-cli
```text
solana 0.12.0
solana-cli 0.20.0
Blockchain, Rebuilt for Scale
USAGE:
solana [FLAGS] [OPTIONS] [SUBCOMMAND]
solana [OPTIONS] <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
@ -194,13 +195,13 @@ OPTIONS:
SUBCOMMANDS:
address Get your public key
airdrop Request lamports
authorize-voter Authorize a new vote signing keypair for the given vote account
balance Get your balance
cancel Cancel a transfer
claim-storage-reward Redeem storage reward credits
cluster-version Get the version of the cluster entrypoint
confirm Confirm transaction by signature
create-replicator-storage-account Create a replicator storage account
create-stake-account Create a stake account
create-storage-mining-pool-account Create mining pool account
create-validator-storage-account Create a validator storage account
create-vote-account Create a vote account
@ -222,12 +223,18 @@ SUBCOMMANDS:
show-stake-account Show the contents of a stake account
show-storage-account Show the contents of a storage account
show-vote-account Show the contents of a vote account
stake-authorize-staker Authorize a new stake signing keypair for the given stake account
stake-authorize-withdrawer Authorize a new withdraw signing keypair for the given stake account
uptime Show the uptime of a validator, based on epoch voting history
validator-info Publish/get Validator info on Solana
vote-authorize-voter Authorize a new vote signing keypair for the given vote account
vote-authorize-withdrawer Authorize a new withdraw signing keypair for the given vote account
withdraw-stake Withdraw the unstaked lamports from the stake account
```
#### solana-address
```text
solana-address
solana-address
Get your public key
USAGE:
@ -243,19 +250,20 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
```
#### solana-airdrop
```text
solana-airdrop
Request a batch of lamports
solana-airdrop
Request lamports
USAGE:
solana airdrop [OPTIONS] <AMOUNT> [unit]
solana airdrop [OPTIONS] <AMOUNT> [UNIT]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
--drone-host <HOST> Drone host to use [default: the --url host]
--drone-port <PORT> Drone port to use [default: 9900]
-u, --url <URL> JSON RPC URL for the solana cluster
@ -263,33 +271,12 @@ OPTIONS:
ARGS:
<AMOUNT> The airdrop amount to request (default unit SOL)
<unit> Specify unit to use for request and balance display [possible values: SOL, lamports]
<UNIT> Specify unit to use for request and balance display [possible values: SOL, lamports]
```
#### solana-balance
```text
solana-authorize-voter
Authorize a new vote signing keypair for the given vote account
USAGE:
solana authorize-voter [OPTIONS] <VOTE ACCOUNT PUBKEY> <CURRENT VOTER KEYPAIR FILE> <NEW VOTER PUBKEY>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<VOTE ACCOUNT PUBKEY> Vote account in which to set the authorized voter
<CURRENT VOTER KEYPAIR FILE> Keypair file for the currently authorized vote signer
<NEW VOTER PUBKEY> New vote signer to authorize
```
```text
solana-balance
solana-balance
Get your balance
USAGE:
@ -309,8 +296,9 @@ ARGS:
<PUBKEY> The public key of the balance to check
```
#### solana-cancel
```text
solana-cancel
solana-cancel
Cancel a transfer
USAGE:
@ -329,8 +317,9 @@ ARGS:
<PROCESS ID> The process id of the transfer to cancel
```
#### solana-claim-storage-reward
```text
solana-claim-storage-reward
solana-claim-storage-reward
Redeem storage reward credits
USAGE:
@ -350,8 +339,9 @@ ARGS:
<STORAGE ACCOUNT PUBKEY> Storage account address to redeem credits for
```
#### solana-cluster-version
```text
solana-cluster-version
solana-cluster-version
Get the version of the cluster entrypoint
USAGE:
@ -367,8 +357,9 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
```
#### solana-confirm
```text
solana-confirm
solana-confirm
Confirm transaction by signature
USAGE:
@ -387,8 +378,9 @@ ARGS:
<SIGNATURE> The transaction signature to confirm
```
#### solana-create-replicator-storage-account
```text
solana-create-replicator-storage-account
solana-create-replicator-storage-account
Create a replicator storage account
USAGE:
@ -404,34 +396,64 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STORAGE ACCOUNT OWNER PUBKEY>
<STORAGE ACCOUNT PUBKEY>
<STORAGE ACCOUNT OWNER PUBKEY>
<STORAGE ACCOUNT PUBKEY>
```
#### solana-create-stake-account
```text
solana-create-storage-mining-pool-account
Create mining pool account
solana-create-stake-account
Create a stake account
USAGE:
solana create-storage-mining-pool-account [OPTIONS] <STORAGE ACCOUNT PUBKEY> <AMOUNT> [unit]
solana create-stake-account [OPTIONS] <STAKE ACCOUNT> <AMOUNT> [UNIT]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
--authorized-staker <PUBKEY> Public key of authorized staker (defaults to wallet)
--authorized-withdrawer <PUBKEY> Public key of the authorized withdrawer (defaults to wallet)
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/wallet/config.yml]
--custodian <PUBKEY> Identity of the custodian (can withdraw before lockup expires)
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
--lockup <SLOT> The slot height at which this account will be available for withdrawal
ARGS:
<STAKE ACCOUNT> Address of the stake account to fund (pubkey or keypair)
<AMOUNT> The amount of send to the vote account (default unit SOL)
<UNIT> Specify unit to use for request [possible values: SOL, lamports]
```
#### solana-create-storage-mining-pool-account
```text
solana-create-storage-mining-pool-account
Create mining pool account
USAGE:
solana create-storage-mining-pool-account [OPTIONS] <STORAGE ACCOUNT PUBKEY> <AMOUNT> [UNIT]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STORAGE ACCOUNT PUBKEY> Storage mining pool account address to fund
<AMOUNT> The amount to assign to the storage mining pool account (default unit SOL)
<unit> Specify unit to use for request [possible values: SOL, lamports]
<UNIT> Specify unit to use for request [possible values: SOL, lamports]
```
#### solana-create-validator-storage-account
```text
solana-create-validator-storage-account
solana-create-validator-storage-account
Create a validator storage account
USAGE:
@ -447,39 +469,45 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STORAGE ACCOUNT OWNER PUBKEY>
<STORAGE ACCOUNT PUBKEY>
<STORAGE ACCOUNT OWNER PUBKEY>
<STORAGE ACCOUNT PUBKEY>
```
#### solana-create-vote-account
```text
solana-create-vote-account
solana-create-vote-account
Create a vote account
USAGE:
solana create-vote-account [OPTIONS] <VOTE ACCOUNT PUBKEY> <VALIDATOR PUBKEY> <LAMPORTS>
solana create-vote-account [OPTIONS] <VOTE ACCOUNT PUBKEY> <VALIDATOR PUBKEY> <AMOUNT> [UNIT]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
--commission <NUM> The commission taken on reward redemption (0-255), default: 0
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
--authorized-voter <PUBKEY> Public key of the authorized voter (defaults to vote account)
--authorized-withdrawer <PUBKEY> Public key of the authorized withdrawer (defaults to wallet)
--commission <NUM> The commission taken on reward redemption (0-255), default: 0
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<VOTE ACCOUNT PUBKEY> Vote account address to fund
<VALIDATOR PUBKEY> Validator that will vote with this account
<LAMPORTS> The amount of lamports to send to the vote account
<AMOUNT> The amount of send to the vote account (default unit SOL)
<UNIT> Specify unit to use for request [possible values: SOL, lamports]
```
#### solana-deactivate-stake
```text
solana-deactivate-stake
solana-deactivate-stake
Deactivate the delegated stake from the stake account
USAGE:
solana deactivate-stake [OPTIONS] <STAKE ACCOUNT KEYPAIR FILE> <PUBKEY>
solana deactivate-stake [OPTIONS] <STAKE ACCOUNT> <VOTE ACCOUNT>
FLAGS:
-h, --help Prints help information
@ -491,35 +519,35 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT KEYPAIR FILE> Keypair file for the stake account, for signing the delegate transaction.
<PUBKEY> The vote account to which the stake is currently delegated
<STAKE ACCOUNT> Stake account to be deactivated.
<VOTE ACCOUNT> The vote account to which the stake is currently delegated
```
#### solana-delegate-stake
```text
solana-delegate-stake
solana-delegate-stake
Delegate stake to a vote account
USAGE:
solana delegate-stake [OPTIONS] <STAKE ACCOUNT KEYPAIR FILE> <VOTE ACCOUNT PUBKEY> <AMOUNT> [unit]
solana delegate-stake [OPTIONS] <STAKE ACCOUNT> <VOTE ACCOUNT>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT KEYPAIR FILE> Keypair file for the new stake account
<VOTE ACCOUNT PUBKEY> The vote account to which the stake will be delegated
<AMOUNT> The amount to delegate (default unit SOL)
<unit> Specify unit to use for request [possible values: SOL, lamports]
<STAKE ACCOUNT> Stake account to delegate
<VOTE ACCOUNT> The vote account to which the stake will be delegated
```
#### solana-deploy
```text
solana-deploy
solana-deploy
Deploy a program
USAGE:
@ -538,8 +566,9 @@ ARGS:
<PATH TO PROGRAM> /path/to/program.o
```
#### solana-fees
```text
solana-fees
solana-fees
Display current cluster fees
USAGE:
@ -555,8 +584,9 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
```
#### solana-get
```text
solana-get
solana-get
Get wallet config settings
USAGE:
@ -575,8 +605,9 @@ ARGS:
<CONFIG_FIELD> Return a specific config setting [possible values: url, keypair]
```
#### solana-get-slot
```text
solana-get-slot
solana-get-slot
Get current slot
USAGE:
@ -592,8 +623,9 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
```
#### solana-get-transaction-count
```text
solana-get-transaction-count
solana-get-transaction-count
Get current transaction count
USAGE:
@ -609,21 +641,34 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
```
#### solana-help
```text
solana-pay
solana-help
Prints this message or the help of the given subcommand(s)
USAGE:
solana help [subcommand]...
ARGS:
<subcommand>... The subcommand whose help message to display
```
#### solana-pay
```text
solana-pay
Send a payment
USAGE:
solana pay [FLAGS] [OPTIONS] <PUBKEY> <AMOUNT> [--] [unit]
solana pay [FLAGS] [OPTIONS] <PUBKEY> <AMOUNT> [--] [UNIT]
FLAGS:
--cancelable
--cancelable
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default:
/Users/tyeraeulberg/.config/solana/wallet/config.yml]
~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
--after <DATETIME> A timestamp after which transaction will execute
@ -631,13 +676,14 @@ OPTIONS:
--require-signature-from <PUBKEY>... Any third party signatures required to unlock the lamports
ARGS:
<PUBKEY> The public key of recipient
<PUBKEY> The pubkey of recipient
<AMOUNT> The amount to send (default unit SOL)
<unit> Specify unit to use for request [possible values: SOL, lamports]
<UNIT> Specify unit to use for request [possible values: SOL, lamports]
```
#### solana-ping
```text
solana-ping
solana-ping
Submit transactions sequentially
USAGE:
@ -648,8 +694,7 @@ FLAGS:
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default:
~/.config/solana/wallet/config.yml]
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-c, --count <NUMBER> Stop after submitting count transactions
-i, --interval <SECONDS> Wait interval seconds between submitting the next transaction [default: 2]
-u, --url <URL> JSON RPC URL for the solana cluster
@ -657,12 +702,13 @@ OPTIONS:
-t, --timeout <SECONDS> Wait up to timeout seconds for transaction confirmation [default: 10]
```
#### solana-redeem-vote-credits
```text
solana-redeem-vote-credits
solana-redeem-vote-credits
Redeem credits in the stake account
USAGE:
solana redeem-vote-credits [OPTIONS] <STAKING ACCOUNT PUBKEY> <VOTE ACCOUNT PUBKEY>
solana redeem-vote-credits [OPTIONS] <STAKE ACCOUNT> <VOTE ACCOUNT>
FLAGS:
-h, --help Prints help information
@ -674,12 +720,13 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKING ACCOUNT PUBKEY> Staking account address to redeem credits for
<VOTE ACCOUNT PUBKEY> The vote account to which the stake was previously delegated.
<STAKE ACCOUNT> Address of the stake account in which to redeem credits
<VOTE ACCOUNT> The vote account to which the stake is currently delegated.
```
#### solana-send-signature
```text
solana-send-signature
solana-send-signature
Send a signature to authorize a transfer
USAGE:
@ -695,12 +742,13 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
ARGS:
<PUBKEY> The public key of recipient
<PUBKEY> The pubkey of recipient
<PROCESS ID> The process id of the transfer to authorize
```
#### solana-send-timestamp
```text
solana-send-timestamp
solana-send-timestamp
Send a timestamp to unlock a transfer
USAGE:
@ -717,12 +765,13 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
ARGS:
<PUBKEY> The public key of recipient
<PUBKEY> The pubkey of recipient
<PROCESS ID> The process id of the transfer to unlock
```
#### solana-set
```text
solana-set
solana-set
Set a wallet config setting
USAGE:
@ -738,8 +787,9 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
```
#### solana-show-account
```text
solana-show-account
solana-show-account
Show the contents of an account
USAGE:
@ -757,19 +807,21 @@ OPTIONS:
-o, --output <FILE> Write the account data to this file
ARGS:
<ACCOUNT PUBKEY> Account public key
<ACCOUNT PUBKEY> Account pubkey
```
#### solana-show-stake-account
```text
solana-show-stake-account
solana-show-stake-account
Show the contents of a stake account
USAGE:
solana show-stake-account [OPTIONS] <STAKE ACCOUNT PUBKEY>
solana show-stake-account [FLAGS] [OPTIONS] <STAKE ACCOUNT>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
-h, --help Prints help information
--lamports Display balance in lamports instead of SOL
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
@ -777,11 +829,12 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT PUBKEY> Stake account public key
<STAKE ACCOUNT> Address of the stake account to display
```
#### solana-show-storage-account
```text
solana-show-storage-account
solana-show-storage-account
Show the contents of a storage account
USAGE:
@ -797,15 +850,38 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STORAGE ACCOUNT PUBKEY> Storage account public key
<STORAGE ACCOUNT PUBKEY> Storage account pubkey
```
#### solana-show-vote-account
```text
solana-show-vote-account
solana-show-vote-account
Show the contents of a vote account
USAGE:
solana show-vote-account [OPTIONS] <VOTE ACCOUNT PUBKEY>
solana show-vote-account [FLAGS] [OPTIONS] <VOTE ACCOUNT PUBKEY>
FLAGS:
-h, --help Prints help information
--lamports Display balance in lamports instead of SOL
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<VOTE ACCOUNT PUBKEY> Vote account pubkey
```
#### solana-stake-authorize-staker
```text
solana-stake-authorize-staker
Authorize a new stake signing keypair for the given stake account
USAGE:
solana stake-authorize-staker [OPTIONS] <STAKE ACCOUNT> <AUTHORIZE PUBKEY>
FLAGS:
-h, --help Prints help information
@ -817,11 +893,58 @@ OPTIONS:
-k, --keypair <PATH> /path/to/id.json
ARGS:
<VOTE ACCOUNT PUBKEY> Vote account public key
<STAKE ACCOUNT> Stake account in which to set the authorized staker
<AUTHORIZE PUBKEY> New authorized staker
```
#### solana-stake-authorize-withdrawer
```text
solana-validator-info
solana-stake-authorize-withdrawer
Authorize a new withdraw signing keypair for the given stake account
USAGE:
solana stake-authorize-withdrawer [OPTIONS] <STAKE ACCOUNT> <AUTHORIZE PUBKEY>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT> Stake account in which to set the authorized withdrawer
<AUTHORIZE PUBKEY> New authorized withdrawer
```
#### solana-uptime
```text
solana-uptime
Show the uptime of a validator, based on epoch voting history
USAGE:
solana uptime [FLAGS] [OPTIONS] <VOTE ACCOUNT PUBKEY>
FLAGS:
--aggregate Aggregate uptime data across span
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
--span <NUM OF EPOCHS> Number of recent epochs to examine
ARGS:
<VOTE ACCOUNT PUBKEY> Vote account pubkey
```
#### solana-validator-info
```text
solana-validator-info
Publish/get Validator info on Solana
USAGE:
@ -842,26 +965,71 @@ SUBCOMMANDS:
publish Publish Validator info on Solana
```
#### solana-vote-authorize-voter
```text
solana-withdraw-stake
Withdraw the unstaked lamports from the stake account
solana-vote-authorize-voter
Authorize a new vote signing keypair for the given vote account
USAGE:
solana withdraw-stake [OPTIONS] <STAKE ACCOUNT KEYPAIR FILE> <DESTINATION PUBKEY> <AMOUNT> [unit]
solana vote-authorize-voter [OPTIONS] <VOTE ACCOUNT PUBKEY> <NEW VOTER PUBKEY>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: /Users/tyeraeulberg/.config/solana/wallet/config.yml]
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT KEYPAIR FILE> Keypair file for the stake account, for signing the withdraw transaction.
<DESTINATION PUBKEY> The account where the lamports should be transfered
<AMOUNT> The amount to withdraw from the stake account (default unit SOL)
<unit> Specify unit to use for request [possible values: SOL, lamports]
<VOTE ACCOUNT PUBKEY> Vote account in which to set the authorized voter
<NEW VOTER PUBKEY> New vote signer to authorize
```
#### solana-vote-authorize-withdrawer
```text
solana-vote-authorize-withdrawer
Authorize a new withdraw signing keypair for the given vote account
USAGE:
solana vote-authorize-withdrawer [OPTIONS] <VOTE ACCOUNT PUBKEY> <NEW WITHDRAWER PUBKEY>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<VOTE ACCOUNT PUBKEY> Vote account in which to set the authorized withdrawer
<NEW WITHDRAWER PUBKEY> New withdrawer to authorize
```
#### solana-withdraw-stake
```text
solana-withdraw-stake
Withdraw the unstaked lamports from the stake account
USAGE:
solana withdraw-stake [OPTIONS] <STAKE ACCOUNT> <DESTINATION ACCOUNT> <AMOUNT> [UNIT]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-C, --config <PATH> Configuration file to use [default: ~/.config/solana/wallet/config.yml]
-u, --url <URL> JSON RPC URL for the solana cluster
-k, --keypair <PATH> /path/to/id.json
ARGS:
<STAKE ACCOUNT> Stake account from which to withdraw
<DESTINATION ACCOUNT> The account to which the lamports should be transfered
<AMOUNT> The amount to withdraw from the stake account (default unit SOL)
<UNIT> Specify unit to use for request [possible values: SOL, lamports]
```

View File

@ -10,15 +10,22 @@ First create a stake account keypair with `solana-keygen`:
$ solana-keygen new -o ~/validator-config/stake-keypair.json
```
and use the cli's `delegate-stake` command to stake your validator with 42 lamports:
and use the cli's `create-stake-account` and `delegate-stake` commands to stake your validator with 42 lamports:
```bash
$ solana delegate-stake ~/validator-config/stake-keypair.json ~/validator-vote-keypair.json 42 lamports
$ solana create-stake-account ~/validator-config/stake-keypair.json 42 lamports
$ solana delegate-stake ~/validator-config/stake-keypair.json ~/validator-vote-keypair.json
```
Note that stakes need to warm up, and warmup increments are applied at Epoch boundaries, so it can take an hour or more for the change to fully take effect.
Assuming your node is voting, now you're up and running and generating validator rewards. You'll want to periodically redeem/claim your rewards:
Stakes can be re-delegated to another node at any time with the same command:
```bash
$ solana delegate-stake ~/validator-config/stake-keypair.json ~/some-other-validator-vote-keypair.json
```
Assuming the node is voting, now you're up and running and generating validator rewards. You'll want to periodically redeem/claim your rewards:
```bash
$ solana redeem-vote-credits ~/validator-config/stake-keypair.json ~/validator-vote-keypair.json
@ -37,4 +44,3 @@ The stake will cool down, deactivate over time. While cooling down, your stake w
Note that a stake account may only be used once, so after deactivation, use the cli's `withdraw-stake` command to recover the previously staked lamports.
Be sure and redeem your credits before withdrawing all your lamports. Once the account is fully withdrawn, the account is destroyed.

View File

@ -1,3 +1,4 @@
use crate::sol_to_lamports;
use clap::ArgMatches;
use solana_sdk::{
pubkey::Pubkey,
@ -43,6 +44,14 @@ pub fn pubkey_of(matches: &ArgMatches<'_>, name: &str) -> Option<Pubkey> {
value_of(matches, name).or_else(|| keypair_of(matches, name).map(|keypair| keypair.pubkey()))
}
pub fn amount_of(matches: &ArgMatches<'_>, name: &str, unit: &str) -> Option<u64> {
if matches.value_of(unit) == Some("lamports") {
value_of(matches, name)
} else {
value_of(matches, name).map(sol_to_lamports)
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -5,6 +5,7 @@ pub mod config;
pub mod display;
pub mod input_parsers;
pub mod input_validators;
pub mod stake;
pub mod validator_info;
pub mod vote;
pub mod wallet;

742
cli/src/stake.rs Normal file
View File

@ -0,0 +1,742 @@
use crate::{
input_parsers::*,
input_validators::*,
wallet::{
build_balance_message, check_account_for_fee, check_unique_pubkeys,
log_instruction_custom_error, ProcessResult, WalletCommand, WalletConfig, WalletError,
},
};
use clap::{App, Arg, ArgMatches, SubCommand};
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
account_utils::State, pubkey::Pubkey, signature::KeypairUtil, system_instruction::SystemError,
transaction::Transaction,
};
use solana_stake_api::{
stake_instruction::{self, StakeError},
stake_state::{Authorized, Lockup, StakeAuthorize, StakeState},
};
use solana_vote_api::vote_state::VoteState;
pub trait StakeSubCommands {
fn stake_subcommands(self) -> Self;
}
impl StakeSubCommands for App<'_, '_> {
fn stake_subcommands(self) -> Self {
self.subcommand(
SubCommand::with_name("create-stake-account")
.about("Create a stake account")
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKE ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("Address of the stake account to fund (pubkey or keypair)")
)
.arg(
Arg::with_name("amount")
.index(2)
.value_name("AMOUNT")
.takes_value(true)
.required(true)
.help("The amount of send to the vote account (default unit SOL)")
)
.arg(
Arg::with_name("unit")
.index(3)
.value_name("UNIT")
.takes_value(true)
.possible_values(&["SOL", "lamports"])
.help("Specify unit to use for request")
)
.arg(
Arg::with_name("custodian")
.long("custodian")
.value_name("PUBKEY")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.help("Identity of the custodian (can withdraw before lockup expires)")
)
.arg(
Arg::with_name("lockup")
.long("lockup")
.value_name("SLOT")
.takes_value(true)
.help("The slot height at which this account will be available for withdrawal")
)
.arg(
Arg::with_name("authorized_staker")
.long("authorized-staker")
.value_name("PUBKEY")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.help("Public key of authorized staker (defaults to wallet)")
)
.arg(
Arg::with_name("authorized_withdrawer")
.long("authorized-withdrawer")
.value_name("PUBKEY")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.help("Public key of the authorized withdrawer (defaults to wallet)")
)
)
.subcommand(
SubCommand::with_name("delegate-stake")
.about("Delegate stake to a vote account")
.arg(
Arg::with_name("force")
.long("force")
.takes_value(false)
.hidden(true) // Don't document this argument to discourage its use
.help("Override vote account sanity checks (use carefully!)")
)
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKE ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("Stake account to delegate")
)
.arg(
Arg::with_name("vote_account_pubkey")
.index(2)
.value_name("VOTE ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("The vote account to which the stake will be delegated")
)
)
.subcommand(
SubCommand::with_name("stake-authorize-staker")
.about("Authorize a new stake signing keypair for the given stake account")
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKE ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("Stake account in which to set the authorized staker")
)
.arg(
Arg::with_name("authorized_pubkey")
.index(2)
.value_name("AUTHORIZE PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("New authorized staker")
)
)
.subcommand(
SubCommand::with_name("stake-authorize-withdrawer")
.about("Authorize a new withdraw signing keypair for the given stake account")
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKE ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("Stake account in which to set the authorized withdrawer")
)
.arg(
Arg::with_name("authorized_pubkey")
.index(2)
.value_name("AUTHORIZE PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("New authorized withdrawer")
)
)
.subcommand(
SubCommand::with_name("deactivate-stake")
.about("Deactivate the delegated stake from the stake account")
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKE ACCOUNT")
.takes_value(true)
.required(true)
.help("Stake account to be deactivated.")
)
.arg(
Arg::with_name("vote_account_pubkey")
.index(2)
.value_name("VOTE ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("The vote account to which the stake is currently delegated")
)
)
.subcommand(
SubCommand::with_name("withdraw-stake")
.about("Withdraw the unstaked lamports from the stake account")
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKE ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("Stake account from which to withdraw")
)
.arg(
Arg::with_name("destination_account_pubkey")
.index(2)
.value_name("DESTINATION ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("The account to which the lamports should be transfered")
)
.arg(
Arg::with_name("amount")
.index(3)
.value_name("AMOUNT")
.takes_value(true)
.required(true)
.help("The amount to withdraw from the stake account (default unit SOL)")
)
.arg(
Arg::with_name("unit")
.index(4)
.value_name("UNIT")
.takes_value(true)
.possible_values(&["SOL", "lamports"])
.help("Specify unit to use for request")
)
)
.subcommand(
SubCommand::with_name("redeem-vote-credits")
.about("Redeem credits in the stake account")
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKE ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("Address of the stake account in which to redeem credits")
)
.arg(
Arg::with_name("vote_account_pubkey")
.index(2)
.value_name("VOTE ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("The vote account to which the stake is currently delegated.")
)
)
.subcommand(
SubCommand::with_name("show-stake-account")
.about("Show the contents of a stake account")
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKE ACCOUNT")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("Address of the stake account to display")
)
.arg(
Arg::with_name("lamports")
.long("lamports")
.takes_value(false)
.help("Display balance in lamports instead of SOL")
)
)
}
}
pub fn parse_stake_create_account(
pubkey: &Pubkey,
matches: &ArgMatches<'_>,
) -> Result<WalletCommand, WalletError> {
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let slot = value_of(&matches, "lockup").unwrap_or(0);
let custodian = pubkey_of(matches, "custodian").unwrap_or_default();
let staker = pubkey_of(matches, "authorized_staker").unwrap_or(*pubkey); // defaults to config
let withdrawer = pubkey_of(matches, "authorized_withdrawer").unwrap_or(*pubkey); // defaults to config
let lamports = amount_of(matches, "amount", "unit").expect("Invalid amount");
Ok(WalletCommand::CreateStakeAccount(
stake_account_pubkey,
Authorized { staker, withdrawer },
Lockup { custodian, slot },
lamports,
))
}
pub fn parse_stake_delegate_stake(matches: &ArgMatches<'_>) -> Result<WalletCommand, WalletError> {
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
let force = matches.is_present("force");
Ok(WalletCommand::DelegateStake(
stake_account_pubkey,
vote_account_pubkey,
force,
))
}
pub fn parse_stake_authorize(
matches: &ArgMatches<'_>,
stake_authorize: StakeAuthorize,
) -> Result<WalletCommand, WalletError> {
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let authorized_pubkey = pubkey_of(matches, "authorized_pubkey").unwrap();
Ok(WalletCommand::StakeAuthorize(
stake_account_pubkey,
authorized_pubkey,
stake_authorize,
))
}
pub fn parse_redeem_vote_credits(matches: &ArgMatches<'_>) -> Result<WalletCommand, WalletError> {
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
Ok(WalletCommand::RedeemVoteCredits(
stake_account_pubkey,
vote_account_pubkey,
))
}
pub fn parse_stake_deactivate_stake(
matches: &ArgMatches<'_>,
) -> Result<WalletCommand, WalletError> {
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
Ok(WalletCommand::DeactivateStake(
stake_account_pubkey,
vote_account_pubkey,
))
}
pub fn parse_stake_withdraw_stake(matches: &ArgMatches<'_>) -> Result<WalletCommand, WalletError> {
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let destination_account_pubkey = pubkey_of(matches, "destination_account_pubkey").unwrap();
let lamports = amount_of(matches, "amount", "unit").expect("Invalid amount");
Ok(WalletCommand::WithdrawStake(
stake_account_pubkey,
destination_account_pubkey,
lamports,
))
}
pub fn parse_show_stake_account(matches: &ArgMatches<'_>) -> Result<WalletCommand, WalletError> {
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let use_lamports_unit = matches.is_present("lamports");
Ok(WalletCommand::ShowStakeAccount {
pubkey: stake_account_pubkey,
use_lamports_unit,
})
}
pub fn process_create_stake_account(
rpc_client: &RpcClient,
config: &WalletConfig,
stake_account_pubkey: &Pubkey,
authorized: &Authorized,
lockup: &Lockup,
lamports: u64,
) -> ProcessResult {
check_unique_pubkeys(
(&config.keypair.pubkey(), "wallet keypair".to_string()),
(stake_account_pubkey, "stake_account_pubkey".to_string()),
)?;
if rpc_client.get_account(&stake_account_pubkey).is_ok() {
return Err(WalletError::BadParameter(format!(
"Unable to create stake account. Stake account already exists: {}",
stake_account_pubkey
))
.into());
}
let ixs = stake_instruction::create_stake_account_with_lockup(
&config.keypair.pubkey(),
stake_account_pubkey,
authorized,
lockup,
lamports,
);
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<SystemError>(result)
}
pub fn process_stake_authorize(
rpc_client: &RpcClient,
config: &WalletConfig,
stake_account_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
stake_authorize: StakeAuthorize,
) -> ProcessResult {
check_unique_pubkeys(
(stake_account_pubkey, "stake_account_pubkey".to_string()),
(authorized_pubkey, "new_authorized_pubkey".to_string()),
)?;
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![stake_instruction::authorize(
stake_account_pubkey, // stake account to update
&config.keypair.pubkey(), // currently authorized
authorized_pubkey, // new stake signer
stake_authorize, // stake or withdraw
)];
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<StakeError>(result)
}
pub fn process_deactivate_stake_account(
rpc_client: &RpcClient,
config: &WalletConfig,
stake_account_pubkey: &Pubkey,
vote_account_pubkey: &Pubkey,
) -> ProcessResult {
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![stake_instruction::deactivate_stake(
stake_account_pubkey,
&config.keypair.pubkey(),
vote_account_pubkey,
)];
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<StakeError>(result)
}
pub fn process_withdraw_stake(
rpc_client: &RpcClient,
config: &WalletConfig,
stake_account_pubkey: &Pubkey,
destination_account_pubkey: &Pubkey,
lamports: u64,
) -> ProcessResult {
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![stake_instruction::withdraw(
stake_account_pubkey,
&config.keypair.pubkey(),
destination_account_pubkey,
lamports,
)];
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<StakeError>(result)
}
pub fn process_redeem_vote_credits(
rpc_client: &RpcClient,
config: &WalletConfig,
stake_account_pubkey: &Pubkey,
vote_account_pubkey: &Pubkey,
) -> ProcessResult {
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![stake_instruction::redeem_vote_credits(
stake_account_pubkey,
vote_account_pubkey,
)];
let mut tx = Transaction::new_signed_with_payer(
ixs,
Some(&config.keypair.pubkey()),
&[&config.keypair],
recent_blockhash,
);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<StakeError>(result)
}
pub fn process_show_stake_account(
rpc_client: &RpcClient,
_config: &WalletConfig,
stake_account_pubkey: &Pubkey,
use_lamports_unit: bool,
) -> ProcessResult {
let stake_account = rpc_client.get_account(stake_account_pubkey)?;
if stake_account.owner != solana_stake_api::id() {
Err(WalletError::RpcRequestError(
format!("{:?} is not a stake account", stake_account_pubkey).to_string(),
))?;
}
fn show_authorized(authorized: &Authorized) {
println!("authorized staker: {}", authorized.staker);
println!("authorized withdrawer: {}", authorized.staker);
}
fn show_lockup(lockup: &Lockup) {
println!("lockup slot: {}", lockup.slot);
println!("lockup custodian: {}", lockup.custodian);
}
match stake_account.state() {
Ok(StakeState::Stake(authorized, lockup, stake)) => {
println!(
"total stake: {}",
build_balance_message(stake_account.lamports, use_lamports_unit)
);
println!("credits observed: {}", stake.credits_observed);
println!(
"delegated stake: {}",
build_balance_message(stake.stake, use_lamports_unit)
);
if stake.voter_pubkey != Pubkey::default() {
println!("delegated voter pubkey: {}", stake.voter_pubkey);
}
println!(
"stake activates starting from epoch: {}",
stake.activation_epoch
);
if stake.deactivation_epoch < std::u64::MAX {
println!(
"stake deactivates starting from epoch: {}",
stake.deactivation_epoch
);
}
show_authorized(&authorized);
show_lockup(&lockup);
Ok("".to_string())
}
Ok(StakeState::RewardsPool) => Ok("Stake account is a rewards pool".to_string()),
Ok(StakeState::Uninitialized) => Ok("Stake account is uninitialized".to_string()),
Ok(StakeState::Initialized(authorized, lockup)) => {
println!("Stake account is undelegated");
show_authorized(&authorized);
show_lockup(&lockup);
Ok("".to_string())
}
Err(err) => Err(WalletError::RpcRequestError(format!(
"Account data could not be deserialized to stake state: {:?}",
err
)))?,
}
}
pub fn process_delegate_stake(
rpc_client: &RpcClient,
config: &WalletConfig,
stake_account_pubkey: &Pubkey,
vote_account_pubkey: &Pubkey,
force: bool,
) -> ProcessResult {
check_unique_pubkeys(
(&config.keypair.pubkey(), "wallet keypair".to_string()),
(stake_account_pubkey, "stake_account_pubkey".to_string()),
)?;
// Sanity check the vote account to ensure it is attached to a validator that has recently
// voted at the tip of the ledger
let vote_account_data = rpc_client
.get_account_data(vote_account_pubkey)
.map_err(|_| {
WalletError::RpcRequestError(format!("Vote account not found: {}", vote_account_pubkey))
})?;
let vote_state = VoteState::deserialize(&vote_account_data).map_err(|_| {
WalletError::RpcRequestError(
"Account data could not be deserialized to vote state".to_string(),
)
})?;
let sanity_check_result = match vote_state.root_slot {
None => Err(WalletError::BadParameter(
"Unable to delegate. Vote account has no root slot".to_string(),
)),
Some(root_slot) => {
let slot = rpc_client.get_slot()?;
if root_slot + solana_sdk::clock::DEFAULT_SLOTS_PER_TURN < slot {
Err(WalletError::BadParameter(
format!(
"Unable to delegate. Vote account root slot ({}) is too old, the current slot is {}", root_slot, slot
)
))
} else {
Ok(())
}
}
};
if sanity_check_result.is_err() {
if !force {
sanity_check_result?;
} else {
println!("--force supplied, ignoring: {:?}", sanity_check_result);
}
}
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![stake_instruction::delegate_stake(
stake_account_pubkey,
&config.keypair.pubkey(),
vote_account_pubkey,
)];
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<StakeError>(result)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::wallet::{app, parse_command};
#[test]
fn test_parse_command() {
let test_commands = app("test", "desc", "version");
let pubkey = Pubkey::new_rand();
let pubkey_string = format!("{}", pubkey);
// // Test AuthorizeStaker Subcommand
// let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
// let keypair = Keypair::new();
// let keypair_file = format!("{}/tmp/keypair_file-{}", out_dir, keypair.pubkey());
// let _ = write_keypair(&keypair, &keypair_file).unwrap();
//
// let test_authorize_staker = test_commands.clone().get_matches_from(vec![
// "test",
// "stake-authorize-staker",
// &pubkey_string,
// &keypair_file,
// &pubkey_string,
// ]);
// assert_eq!(
// parse_command(&pubkey, &test_authorize_staker).unwrap(),
// WalletCommand::StakeAuthorize(pubkey, keypair, pubkey, StakeAuthorize::Staker)
// );
// fs::remove_file(&keypair_file).unwrap();
// Test CreateVoteAccount SubCommand
let custodian = Pubkey::new_rand();
let custodian_string = format!("{}", custodian);
let authorized = Pubkey::new_rand();
let authorized_string = format!("{}", authorized);
let test_create_stake_account = test_commands.clone().get_matches_from(vec![
"test",
"create-stake-account",
&pubkey_string,
"50",
"--authorized-staker",
&authorized_string,
"--authorized-withdrawer",
&authorized_string,
"--custodian",
&custodian_string,
"--lockup",
"43",
"lamports",
]);
assert_eq!(
parse_command(&pubkey, &test_create_stake_account).unwrap(),
WalletCommand::CreateStakeAccount(
pubkey,
Authorized {
staker: authorized,
withdrawer: authorized,
},
Lockup {
slot: 43,
custodian,
},
50
)
);
let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
"test",
"create-stake-account",
&pubkey_string,
"50",
"lamports",
]);
assert_eq!(
parse_command(&pubkey, &test_create_stake_account2).unwrap(),
WalletCommand::CreateStakeAccount(
pubkey,
Authorized {
staker: pubkey,
withdrawer: pubkey,
},
Lockup {
slot: 0,
custodian: Pubkey::default(),
},
50
)
);
// Test DelegateStake Subcommand
let stake_pubkey = Pubkey::new_rand();
let stake_pubkey_string = stake_pubkey.to_string();
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
"test",
"delegate-stake",
&stake_pubkey_string,
&pubkey_string,
]);
assert_eq!(
parse_command(&pubkey, &test_delegate_stake).unwrap(),
WalletCommand::DelegateStake(stake_pubkey, pubkey, false,)
);
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
"test",
"delegate-stake",
"--force",
&stake_pubkey_string,
&pubkey_string,
]);
assert_eq!(
parse_command(&pubkey, &test_delegate_stake).unwrap(),
WalletCommand::DelegateStake(stake_pubkey, pubkey, true)
);
// Test WithdrawStake Subcommand
let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
"test",
"withdraw-stake",
&stake_pubkey_string,
&pubkey_string,
"42",
"lamports",
]);
assert_eq!(
parse_command(&pubkey, &test_withdraw_stake).unwrap(),
WalletCommand::WithdrawStake(stake_pubkey, pubkey, 42)
);
// Test DeactivateStake Subcommand
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
"test",
"deactivate-stake",
&stake_pubkey_string,
&pubkey_string,
]);
assert_eq!(
parse_command(&pubkey, &test_deactivate_stake).unwrap(),
WalletCommand::DeactivateStake(stake_pubkey, pubkey)
);
}
// TODO: Add process tests
}

View File

@ -1,16 +1,14 @@
use crate::{
input_parsers::*,
wallet::{
check_account_for_fee, check_unique_pubkeys, log_instruction_custom_error, ProcessResult,
WalletCommand, WalletConfig, WalletError,
build_balance_message, check_account_for_fee, check_unique_pubkeys,
log_instruction_custom_error, ProcessResult, WalletCommand, WalletConfig, WalletError,
},
};
use clap::{value_t_or_exit, ArgMatches};
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, KeypairUtil},
system_instruction::SystemError,
pubkey::Pubkey, signature::KeypairUtil, system_instruction::SystemError,
transaction::Transaction,
};
use solana_vote_api::{
@ -18,19 +16,17 @@ use solana_vote_api::{
vote_state::{VoteAuthorize, VoteInit, VoteState},
};
pub fn parse_vote_create_account(matches: &ArgMatches<'_>) -> Result<WalletCommand, WalletError> {
pub fn parse_vote_create_account(
pubkey: &Pubkey,
matches: &ArgMatches<'_>,
) -> Result<WalletCommand, WalletError> {
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
let node_pubkey = pubkey_of(matches, "node_pubkey").unwrap();
let commission = value_of(&matches, "commission").unwrap_or(0);
let authorized_voter = pubkey_of(matches, "authorized_voter").unwrap_or(vote_account_pubkey);
let authorized_withdrawer =
pubkey_of(matches, "authorized_withdrawer").unwrap_or(vote_account_pubkey);
let authorized_withdrawer = pubkey_of(matches, "authorized_withdrawer").unwrap_or(*pubkey);
let lamports = crate::wallet::parse_amount_lamports(
matches.value_of("amount").unwrap(),
matches.value_of("unit"),
)
.map_err(|err| WalletError::BadParameter(format!("Invalid amount: {:?}", err)))?;
let lamports = amount_of(matches, "amount", "unit").expect("Invalid amount");
Ok(WalletCommand::CreateVoteAccount(
vote_account_pubkey,
@ -49,12 +45,10 @@ pub fn parse_vote_authorize(
vote_authorize: VoteAuthorize,
) -> Result<WalletCommand, WalletError> {
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
let authorized_keypair = keypair_of(matches, "authorized_keypair_file").unwrap();
let new_authorized_pubkey = pubkey_of(matches, "new_authorized_pubkey").unwrap();
Ok(WalletCommand::VoteAuthorize(
vote_account_pubkey,
authorized_keypair,
new_authorized_pubkey,
vote_authorize,
))
@ -103,7 +97,6 @@ pub fn process_vote_authorize(
rpc_client: &RpcClient,
config: &WalletConfig,
vote_account_pubkey: &Pubkey,
authorized_keypair: &Keypair,
new_authorized_pubkey: &Pubkey,
vote_authorize: VoteAuthorize,
) -> ProcessResult {
@ -113,21 +106,15 @@ pub fn process_vote_authorize(
)?;
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![vote_instruction::authorize(
vote_account_pubkey, // vote account to update
&authorized_keypair.pubkey(), // current authorized voter (often the vote account itself)
new_authorized_pubkey, // new vote signer
vote_authorize, // vote or withdraw
vote_account_pubkey, // vote account to update
&config.keypair.pubkey(), // current authorized voter
new_authorized_pubkey, // new vote signer/withdrawer
vote_authorize, // vote or withdraw
)];
let mut tx = Transaction::new_signed_with_payer(
ixs,
Some(&config.keypair.pubkey()),
&[&config.keypair, &authorized_keypair],
recent_blockhash,
);
let mut tx = Transaction::new_signed_instructions(&[&config.keypair], ixs, recent_blockhash);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result =
rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, &authorized_keypair]);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<VoteError>(result)
}
@ -168,7 +155,7 @@ pub fn process_show_vote_account(
println!(
"account balance: {}",
crate::wallet::build_balance_message(vote_account.lamports, use_lamports_unit)
build_balance_message(vote_account.lamports, use_lamports_unit)
);
println!("node id: {}", vote_state.node_pubkey);
println!("authorized voter: {}", vote_state.authorized_voter);
@ -290,33 +277,23 @@ pub fn process_uptime(
mod tests {
use super::*;
use crate::wallet::{app, parse_command};
use solana_sdk::signature::write_keypair;
use std::fs;
#[test]
fn test_parse_command() {
let test_commands = app("test", "desc", "version");
let pubkey = Pubkey::new_rand();
let pubkey_string = format!("{}", pubkey);
// Test AuthorizeVoter Subcommand
let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
let keypair = Keypair::new();
let keypair_file = format!("{}/tmp/keypair_file-{}", out_dir, keypair.pubkey());
let _ = write_keypair(&keypair, &keypair_file).unwrap();
let pubkey_string = pubkey.to_string();
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
"test",
"vote-authorize-voter",
&pubkey_string,
&keypair_file,
&pubkey_string,
]);
assert_eq!(
parse_command(&pubkey, &test_authorize_voter).unwrap(),
WalletCommand::VoteAuthorize(pubkey, keypair, pubkey, VoteAuthorize::Voter)
WalletCommand::VoteAuthorize(pubkey, pubkey, VoteAuthorize::Voter)
);
fs::remove_file(&keypair_file).unwrap();
// Test CreateVoteAccount SubCommand
let node_pubkey = Pubkey::new_rand();

View File

@ -1,6 +1,6 @@
use crate::{
display::println_name_value, input_parsers::*, input_validators::*, lamports_to_sol,
sol_to_lamports, validator_info::*, vote::*,
display::println_name_value, input_parsers::*, input_validators::*, lamports_to_sol, stake::*,
validator_info::*, vote::*,
};
use chrono::prelude::*;
use clap::{value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
@ -29,12 +29,9 @@ use solana_sdk::{
system_transaction,
transaction::{Transaction, TransactionError},
};
use solana_stake_api::{
stake_instruction::{self, StakeError},
stake_state::{Authorized, Lockup},
};
use solana_stake_api::stake_state::{Authorized, Lockup, StakeAuthorize};
use solana_storage_api::storage_instruction;
use solana_vote_api::vote_state::{VoteAuthorize, VoteInit, VoteState};
use solana_vote_api::vote_state::{VoteAuthorize, VoteInit};
use std::{
collections::VecDeque,
fs::File,
@ -67,7 +64,7 @@ pub enum WalletCommand {
},
Cancel(Pubkey),
Confirm(Signature),
VoteAuthorize(Pubkey, Keypair, Pubkey, VoteAuthorize),
VoteAuthorize(Pubkey, Pubkey, VoteAuthorize),
CreateVoteAccount(Pubkey, VoteInit, u64),
ShowAccount {
pubkey: Pubkey,
@ -83,9 +80,11 @@ pub enum WalletCommand {
aggregate: bool,
span: Option<u64>,
},
DelegateStake(Keypair, Pubkey, u64, Authorized, bool),
WithdrawStake(Keypair, Pubkey, u64),
DeactivateStake(Keypair, Pubkey),
CreateStakeAccount(Pubkey, Authorized, Lockup, u64),
StakeAuthorize(Pubkey, Pubkey, StakeAuthorize),
DelegateStake(Pubkey, Pubkey, bool),
WithdrawStake(Pubkey, Pubkey, u64),
DeactivateStake(Pubkey, Pubkey),
RedeemVoteCredits(Pubkey, Pubkey),
ShowStakeAccount {
pubkey: Pubkey,
@ -202,10 +201,7 @@ pub fn parse_command(
} else {
None
};
let lamports = parse_amount_lamports(
airdrop_matches.value_of("amount").unwrap(),
airdrop_matches.value_of("unit"),
)?;
let lamports = amount_of(airdrop_matches, "amount", "unit").expect("Invalid amount");
let use_lamports_unit = airdrop_matches.value_of("unit").is_some()
&& airdrop_matches.value_of("unit").unwrap() == "lamports";
Ok(WalletCommand::Airdrop {
@ -246,7 +242,7 @@ pub fn parse_command(
use_lamports_unit,
})
}
("create-vote-account", Some(matches)) => parse_vote_create_account(matches),
("create-vote-account", Some(matches)) => parse_vote_create_account(pubkey, matches),
("vote-authorize-voter", Some(matches)) => {
parse_vote_authorize(matches, VoteAuthorize::Voter)
}
@ -255,61 +251,18 @@ pub fn parse_command(
}
("show-vote-account", Some(matches)) => parse_vote_get_account_command(matches),
("uptime", Some(matches)) => parse_vote_uptime_command(matches),
("delegate-stake", Some(matches)) => {
let stake_account_keypair = keypair_of(matches, "stake_account_keypair_file").unwrap();
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
let lamports = parse_amount_lamports(
matches.value_of("amount").unwrap(),
matches.value_of("unit"),
)?;
let authorized = Authorized::auto(&stake_account_keypair.pubkey());
let force = matches.is_present("force");
Ok(WalletCommand::DelegateStake(
stake_account_keypair,
vote_account_pubkey,
lamports,
authorized,
force,
))
("create-stake-account", Some(matches)) => parse_stake_create_account(pubkey, matches),
("delegate-stake", Some(matches)) => parse_stake_delegate_stake(matches),
("withdraw-stake", Some(matches)) => parse_stake_withdraw_stake(matches),
("deactivate-stake", Some(matches)) => parse_stake_deactivate_stake(matches),
("stake-authorize-staker", Some(matches)) => {
parse_stake_authorize(matches, StakeAuthorize::Staker)
}
("withdraw-stake", Some(matches)) => {
let stake_account_keypair = keypair_of(matches, "stake_account_keypair_file").unwrap();
let destination_account_pubkey =
pubkey_of(matches, "destination_account_pubkey").unwrap();
let lamports = parse_amount_lamports(
matches.value_of("amount").unwrap(),
matches.value_of("unit"),
)?;
Ok(WalletCommand::WithdrawStake(
stake_account_keypair,
destination_account_pubkey,
lamports,
))
}
("deactivate-stake", Some(matches)) => {
let stake_account_keypair = keypair_of(matches, "stake_account_keypair_file").unwrap();
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
Ok(WalletCommand::DeactivateStake(
stake_account_keypair,
vote_account_pubkey,
))
}
("redeem-vote-credits", Some(matches)) => {
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
Ok(WalletCommand::RedeemVoteCredits(
stake_account_pubkey,
vote_account_pubkey,
))
}
("show-stake-account", Some(matches)) => {
let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap();
let use_lamports_unit = matches.is_present("lamports");
Ok(WalletCommand::ShowStakeAccount {
pubkey: stake_account_pubkey,
use_lamports_unit,
})
("stake-authorize-withdrawer", Some(matches)) => {
parse_stake_authorize(matches, StakeAuthorize::Withdrawer)
}
("redeem-vote-credits", Some(matches)) => parse_redeem_vote_credits(matches),
("show-stake-account", Some(matches)) => parse_show_stake_account(matches),
("create-replicator-storage-account", Some(matches)) => {
let account_owner = pubkey_of(matches, "storage_account_owner").unwrap();
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
@ -349,10 +302,7 @@ pub fn parse_command(
("get-epoch-info", Some(_matches)) => Ok(WalletCommand::GetEpochInfo),
("get-transaction-count", Some(_matches)) => Ok(WalletCommand::GetTransactionCount),
("pay", Some(pay_matches)) => {
let lamports = parse_amount_lamports(
pay_matches.value_of("amount").unwrap(),
pay_matches.value_of("unit"),
)?;
let lamports = amount_of(pay_matches, "amount", "unit").expect("Invalid amount");
let to = value_of(&pay_matches, "to").unwrap_or(*pubkey);
let timestamp = if pay_matches.is_present("timestamp") {
// Parse input for serde_json
@ -589,231 +539,6 @@ fn process_show_account(
Ok("".to_string())
}
fn process_deactivate_stake_account(
rpc_client: &RpcClient,
config: &WalletConfig,
stake_account_keypair: &Keypair,
vote_account_pubkey: &Pubkey,
) -> ProcessResult {
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs =
stake_instruction::deactivate_stake(&stake_account_keypair.pubkey(), vote_account_pubkey);
let mut tx = Transaction::new_signed_with_payer(
vec![ixs],
Some(&config.keypair.pubkey()),
&[&config.keypair, &stake_account_keypair],
recent_blockhash,
);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client
.send_and_confirm_transaction(&mut tx, &[&config.keypair, &stake_account_keypair]);
log_instruction_custom_error::<StakeError>(result)
}
fn process_delegate_stake(
rpc_client: &RpcClient,
config: &WalletConfig,
stake_account_keypair: &Keypair,
vote_account_pubkey: &Pubkey,
lamports: u64,
authorized: &Authorized,
force: bool,
) -> ProcessResult {
check_unique_pubkeys(
(&config.keypair.pubkey(), "wallet keypair".to_string()),
(
&stake_account_keypair.pubkey(),
"stake_account_keypair".to_string(),
),
)?;
if rpc_client
.get_account(&stake_account_keypair.pubkey())
.is_ok()
{
return Err(WalletError::BadParameter(format!(
"Unable to delegate. Stake account already exists: {}",
stake_account_keypair.pubkey()
))
.into());
}
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = stake_instruction::create_stake_account_and_delegate_stake(
&config.keypair.pubkey(),
&stake_account_keypair.pubkey(),
vote_account_pubkey,
lamports,
authorized,
);
// Sanity check the vote account to ensure it is attached to a validator that has recently
// voted at the tip of the ledger
let vote_account_data = rpc_client
.get_account_data(vote_account_pubkey)
.map_err(|_| {
WalletError::RpcRequestError(format!("Vote account not found: {}", vote_account_pubkey))
})?;
let vote_state = VoteState::deserialize(&vote_account_data).map_err(|_| {
WalletError::RpcRequestError(
"Account data could not be deserialized to vote state".to_string(),
)
})?;
let sanity_check_result = match vote_state.root_slot {
None => Err(WalletError::BadParameter(
"Unable to delegate. Vote account has no root slot".to_string(),
)),
Some(root_slot) => {
let slot = rpc_client.get_slot()?;
if root_slot + solana_sdk::clock::DEFAULT_SLOTS_PER_TURN < slot {
Err(WalletError::BadParameter(
format!(
"Unable to delegate. Vote account root slot ({}) is too old, the current slot is {}", root_slot, slot
)
))
} else {
Ok(())
}
}
};
if sanity_check_result.is_err() {
if !force {
sanity_check_result?;
} else {
println!("--force supplied, ignoring: {:?}", sanity_check_result);
}
}
let mut tx = Transaction::new_signed_with_payer(
ixs,
Some(&config.keypair.pubkey()),
&[&config.keypair, &stake_account_keypair],
recent_blockhash,
);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client
.send_and_confirm_transaction(&mut tx, &[&config.keypair, &stake_account_keypair]);
log_instruction_custom_error::<StakeError>(result)
}
fn process_withdraw_stake(
rpc_client: &RpcClient,
config: &WalletConfig,
stake_account_keypair: &Keypair,
destination_account_pubkey: &Pubkey,
lamports: u64,
) -> ProcessResult {
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![stake_instruction::withdraw(
&stake_account_keypair.pubkey(),
destination_account_pubkey,
lamports,
)];
let mut tx = Transaction::new_signed_with_payer(
ixs,
Some(&config.keypair.pubkey()),
&[&config.keypair, &stake_account_keypair],
recent_blockhash,
);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client
.send_and_confirm_transaction(&mut tx, &[&config.keypair, &stake_account_keypair]);
log_instruction_custom_error::<StakeError>(result)
}
fn process_redeem_vote_credits(
rpc_client: &RpcClient,
config: &WalletConfig,
stake_account_pubkey: &Pubkey,
vote_account_pubkey: &Pubkey,
) -> ProcessResult {
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
let ixs = vec![stake_instruction::redeem_vote_credits(
stake_account_pubkey,
vote_account_pubkey,
)];
let mut tx = Transaction::new_signed_with_payer(
ixs,
Some(&config.keypair.pubkey()),
&[&config.keypair],
recent_blockhash,
);
check_account_for_fee(rpc_client, config, &fee_calculator, &tx.message)?;
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair]);
log_instruction_custom_error::<StakeError>(result)
}
fn process_show_stake_account(
rpc_client: &RpcClient,
_config: &WalletConfig,
stake_account_pubkey: &Pubkey,
use_lamports_unit: bool,
) -> ProcessResult {
use solana_stake_api::stake_state::StakeState;
let stake_account = rpc_client.get_account(stake_account_pubkey)?;
if stake_account.owner != solana_stake_api::id() {
Err(WalletError::RpcRequestError(
format!("{:?} is not a stake account", stake_account_pubkey).to_string(),
))?;
}
fn show_authorized(authorized: &Authorized) {
println!("authorized staker: {}", authorized.staker);
println!("authorized withdrawer: {}", authorized.staker);
}
fn show_lockup(lockup: &Lockup) {
println!("lockup slot: {}", lockup.slot);
println!("lockup custodian: {}", lockup.custodian);
}
match stake_account.state() {
Ok(StakeState::Stake(authorized, lockup, stake)) => {
println!(
"total stake: {}",
build_balance_message(stake_account.lamports, use_lamports_unit)
);
println!("credits observed: {}", stake.credits_observed);
println!(
"delegated stake: {}",
build_balance_message(stake.stake, use_lamports_unit)
);
if stake.voter_pubkey != Pubkey::default() {
println!("delegated voter pubkey: {}", stake.voter_pubkey);
}
println!(
"stake activates starting from epoch: {}",
stake.activation_epoch
);
if stake.deactivation_epoch < std::u64::MAX {
println!(
"stake deactivates starting from epoch: {}",
stake.deactivation_epoch
);
}
show_authorized(&authorized);
show_lockup(&lockup);
Ok("".to_string())
}
Ok(StakeState::RewardsPool) => Ok("Stake account is a rewards pool".to_string()),
Ok(StakeState::Uninitialized) => Ok("Stake account is uninitialized".to_string()),
Ok(StakeState::Initialized(authorized, lockup)) => {
println!("Stake account is undelegated");
show_authorized(&authorized);
show_lockup(&lockup);
Ok("".to_string())
}
Err(err) => Err(WalletError::RpcRequestError(format!(
"Account data could not be deserialized to stake state: {:?}",
err
)))?,
}
}
fn process_create_replicator_storage_account(
rpc_client: &RpcClient,
config: &WalletConfig,
@ -1374,14 +1099,12 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
WalletCommand::VoteAuthorize(
vote_account_pubkey,
authorized_keypair,
new_authorized_pubkey,
vote_authorize,
) => process_vote_authorize(
&rpc_client,
config,
&vote_account_pubkey,
&authorized_keypair,
&new_authorized_pubkey,
*vote_authorize,
),
@ -1414,40 +1137,56 @@ pub fn process_command(config: &WalletConfig) -> ProcessResult {
span,
} => process_uptime(&rpc_client, config, &vote_account_pubkey, *aggregate, *span),
WalletCommand::DelegateStake(
stake_account_keypair,
vote_account_pubkey,
lamports,
authorized,
force,
) => process_delegate_stake(
// Create stake account
WalletCommand::CreateStakeAccount(stake_account_pubkey, authorized, lockup, lamports) => {
process_create_stake_account(
&rpc_client,
config,
&stake_account_pubkey,
&authorized,
lockup,
*lamports,
)
}
WalletCommand::DelegateStake(stake_account_pubkey, vote_account_pubkey, force) => {
process_delegate_stake(
&rpc_client,
config,
&stake_account_pubkey,
&vote_account_pubkey,
*force,
)
}
WalletCommand::StakeAuthorize(
stake_account_pubkey,
new_authorized_pubkey,
stake_authorize,
) => process_stake_authorize(
&rpc_client,
config,
&stake_account_keypair,
&vote_account_pubkey,
*lamports,
&authorized,
*force,
&stake_account_pubkey,
&new_authorized_pubkey,
*stake_authorize,
),
WalletCommand::WithdrawStake(
stake_account_keypair,
stake_account_pubkey,
destination_account_pubkey,
lamports,
) => process_withdraw_stake(
&rpc_client,
config,
&stake_account_keypair,
&stake_account_pubkey,
&destination_account_pubkey,
*lamports,
),
// Deactivate stake account
WalletCommand::DeactivateStake(stake_account_keypair, vote_account_pubkey) => {
WalletCommand::DeactivateStake(stake_account_pubkey, vote_account_pubkey) => {
process_deactivate_stake_account(
&rpc_client,
config,
&stake_account_keypair,
&stake_account_pubkey,
&vote_account_pubkey,
)
}
@ -1657,17 +1396,6 @@ pub(crate) fn build_balance_message(lamports: u64, use_lamports_unit: bool) -> S
}
}
pub(crate) fn parse_amount_lamports(
amount: &str,
use_lamports_unit: Option<&str>,
) -> Result<u64, Box<dyn error::Error>> {
if use_lamports_unit.is_some() && use_lamports_unit.unwrap() == "lamports" {
Ok(amount.parse()?)
} else {
Ok(sol_to_lamports(amount.parse()?))
}
}
pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, 'v> {
App::new(name)
.about(about)
@ -1704,6 +1432,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.arg(
Arg::with_name("unit")
.index(2)
.value_name("UNIT")
.takes_value(true)
.possible_values(&["SOL", "lamports"])
.help("Specify unit to use for request and balance display"),
@ -1764,18 +1493,9 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.validator(is_pubkey_or_keypair)
.help("Vote account in which to set the authorized voter"),
)
.arg(
Arg::with_name("authorized_keypair_file")
.index(2)
.value_name("CURRENT VOTER KEYPAIR FILE")
.takes_value(true)
.required(true)
.validator(is_keypair)
.help("Keypair file for the currently authorized vote signer"),
)
.arg(
Arg::with_name("new_authorized_pubkey")
.index(3)
.index(2)
.value_name("NEW VOTER PUBKEY")
.takes_value(true)
.required(true)
@ -1795,18 +1515,9 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.validator(is_pubkey_or_keypair)
.help("Vote account in which to set the authorized withdrawer"),
)
.arg(
Arg::with_name("authorized_keypair_file")
.index(2)
.value_name("CURRENT WITHDRAWER KEYPAIR FILE")
.takes_value(true)
.required(true)
.validator(is_keypair)
.help("Keypair file for the currently authorized withdrawer"),
)
.arg(
Arg::with_name("new_authorized_pubkey")
.index(3)
.index(2)
.value_name("NEW WITHDRAWER PUBKEY")
.takes_value(true)
.required(true)
@ -1846,6 +1557,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.arg(
Arg::with_name("unit")
.index(4)
.value_name("UNIT")
.takes_value(true)
.possible_values(&["SOL", "lamports"])
.help("Specify unit to use for request"),
@ -1863,7 +1575,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.value_name("PUBKEY")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.help("Public key of the authorized voter (defaults to vote account pubkey)"),
.help("Public key of the authorized voter (defaults to vote account)"),
)
.arg(
Arg::with_name("authorized_withdrawer")
@ -1871,10 +1583,8 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.value_name("PUBKEY")
.takes_value(true)
.validator(is_pubkey_or_keypair)
.help("Public key of the authorized withdrawer (defaults to vote account pubkey)"),
)
,
.help("Public key of the authorized withdrawer (defaults to wallet)"),
),
)
.subcommand(
SubCommand::with_name("show-account")
@ -1947,149 +1657,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.help("Aggregate uptime data across span")
),
)
.subcommand(
SubCommand::with_name("delegate-stake")
.about("Delegate stake to a vote account")
.arg(
Arg::with_name("force")
.long("force")
.takes_value(false)
.hidden(true) // Don't document this argument to discourage its use
.help("Override vote account sanity checks (use carefully!)"),
)
.arg(
Arg::with_name("stake_account_keypair_file")
.index(1)
.value_name("STAKE ACCOUNT KEYPAIR FILE")
.takes_value(true)
.required(true)
.validator(is_keypair)
.help("Keypair file for the new stake account"),
)
.arg(
Arg::with_name("vote_account_pubkey")
.index(2)
.value_name("VOTE ACCOUNT PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("The vote account to which the stake will be delegated"),
)
.arg(
Arg::with_name("amount")
.index(3)
.value_name("AMOUNT")
.takes_value(true)
.required(true)
.help("The amount to delegate (default unit SOL)"),
)
.arg(
Arg::with_name("unit")
.index(4)
.takes_value(true)
.possible_values(&["SOL", "lamports"])
.help("Specify unit to use for request"),
),
)
.subcommand(
SubCommand::with_name("deactivate-stake")
.about("Deactivate the delegated stake from the stake account")
.arg(
Arg::with_name("stake_account_keypair_file")
.index(1)
.value_name("STAKE ACCOUNT KEYPAIR FILE")
.takes_value(true)
.required(true)
.help("Keypair file for the stake account, for signing the delegate transaction."),
)
.arg(
Arg::with_name("vote_account_pubkey")
.index(2)
.value_name("PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("The vote account to which the stake is currently delegated"),
)
)
.subcommand(
SubCommand::with_name("withdraw-stake")
.about("Withdraw the unstaked lamports from the stake account")
.arg(
Arg::with_name("stake_account_keypair_file")
.index(1)
.value_name("STAKE ACCOUNT KEYPAIR FILE")
.takes_value(true)
.required(true)
.validator(is_keypair)
.help("Keypair file for the stake account, for signing the withdraw transaction."),
)
.arg(
Arg::with_name("destination_account_pubkey")
.index(2)
.value_name("DESTINATION PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("The account where the lamports should be transfered"),
)
.arg(
Arg::with_name("amount")
.index(3)
.value_name("AMOUNT")
.takes_value(true)
.required(true)
.help("The amount to withdraw from the stake account (default unit SOL)"),
)
.arg(
Arg::with_name("unit")
.index(4)
.takes_value(true)
.possible_values(&["SOL", "lamports"])
.help("Specify unit to use for request"),
),
)
.subcommand(
SubCommand::with_name("redeem-vote-credits")
.about("Redeem credits in the stake account")
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKING ACCOUNT PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("Staking account address to redeem credits for"),
)
.arg(
Arg::with_name("vote_account_pubkey")
.index(2)
.value_name("VOTE ACCOUNT PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("The vote account to which the stake was previously delegated."),
),
)
.subcommand(
SubCommand::with_name("show-stake-account")
.about("Show the contents of a stake account")
.arg(
Arg::with_name("stake_account_pubkey")
.index(1)
.value_name("STAKE ACCOUNT PUBKEY")
.takes_value(true)
.required(true)
.validator(is_pubkey_or_keypair)
.help("Stake account pubkey"),
)
.arg(
Arg::with_name("lamports")
.long("lamports")
.takes_value(false)
.help("Display balance in lamports instead of SOL"),
),
)
.stake_subcommands()
.subcommand(
SubCommand::with_name("create-storage-mining-pool-account")
.about("Create mining pool account")
@ -2113,6 +1681,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.arg(
Arg::with_name("unit")
.index(3)
.value_name("UNIT")
.takes_value(true)
.possible_values(&["SOL", "lamports"])
.help("Specify unit to use for request"),
@ -2243,6 +1812,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.arg(
Arg::with_name("unit")
.index(3)
.value_name("UNIT")
.takes_value(true)
.possible_values(&["SOL", "lamports"])
.help("Specify unit to use for request"),
@ -2447,6 +2017,29 @@ mod tests {
};
use std::path::PathBuf;
fn make_tmp_path(name: &str) -> String {
let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
let keypair = Keypair::new();
let path = format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey());
// whack any possible collision
let _ignored = std::fs::remove_dir_all(&path);
// whack any possible collision
let _ignored = std::fs::remove_file(&path);
path
}
#[test]
#[should_panic]
fn test_bad_amount() {
let test_commands = app("test", "desc", "version");
let test_bad_airdrop = test_commands.get_matches_from(vec!["test", "airdrop", "notint"]);
let pubkey = Pubkey::new_rand();
let _ignored = parse_command(&pubkey, &test_bad_airdrop).unwrap();
}
#[test]
fn test_wallet_parse_command() {
let test_commands = app("test", "desc", "version");
@ -2472,10 +2065,6 @@ mod tests {
use_lamports_unit: true,
}
);
let test_bad_airdrop = test_commands
.clone()
.get_matches_from(vec!["test", "airdrop", "notint"]);
assert!(parse_command(&pubkey, &test_bad_airdrop).is_err());
// Test Balance Subcommand, incl pubkey and keypair-file inputs
let keypair_file = make_tmp_path("keypair_file");
@ -2533,97 +2122,6 @@ mod tests {
.get_matches_from(vec!["test", "confirm", "deadbeef"]);
assert!(parse_command(&pubkey, &test_bad_signature).is_err());
// Test DelegateStake Subcommand
fn make_tmp_path(name: &str) -> String {
let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
let keypair = Keypair::new();
let path = format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey());
// whack any possible collision
let _ignored = std::fs::remove_dir_all(&path);
// whack any possible collision
let _ignored = std::fs::remove_file(&path);
path
}
let keypair_file = make_tmp_path("keypair_file");
gen_keypair_file(&keypair_file).unwrap();
let keypair = read_keypair(&keypair_file).unwrap();
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
"test",
"delegate-stake",
&keypair_file,
&pubkey_string,
"42",
"lamports",
]);
let stake_pubkey = keypair.pubkey();
assert_eq!(
parse_command(&pubkey, &test_delegate_stake).unwrap(),
WalletCommand::DelegateStake(
keypair,
pubkey,
42,
Authorized::auto(&stake_pubkey),
false,
)
);
let keypair = read_keypair(&keypair_file).unwrap();
let test_delegate_stake = test_commands.clone().get_matches_from(vec![
"test",
"delegate-stake",
"--force",
&keypair_file,
&pubkey_string,
"42",
"lamports",
]);
let stake_pubkey = keypair.pubkey();
assert_eq!(
parse_command(&pubkey, &test_delegate_stake).unwrap(),
WalletCommand::DelegateStake(
keypair,
pubkey,
42,
Authorized::auto(&stake_pubkey),
true
)
);
// Test WithdrawStake Subcommand
let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
"test",
"withdraw-stake",
&keypair_file,
&pubkey_string,
"42",
"lamports",
]);
let keypair = read_keypair(&keypair_file).unwrap();
assert_eq!(
parse_command(&pubkey, &test_withdraw_stake).unwrap(),
WalletCommand::WithdrawStake(keypair, pubkey, 42)
);
// Test DeactivateStake Subcommand
let keypair_file = make_tmp_path("keypair_file");
gen_keypair_file(&keypair_file).unwrap();
let keypair = read_keypair(&keypair_file).unwrap();
let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
"test",
"deactivate-stake",
&keypair_file,
&pubkey_string,
]);
assert_eq!(
parse_command(&pubkey, &test_deactivate_stake).unwrap(),
WalletCommand::DeactivateStake(keypair, pubkey)
);
// Test Deploy Subcommand
let test_deploy =
test_commands
@ -2831,36 +2329,35 @@ mod tests {
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
let bob_keypair = Keypair::new();
let new_authorized_pubkey = Pubkey::new_rand();
config.command = WalletCommand::VoteAuthorize(
config.command =
WalletCommand::VoteAuthorize(bob_pubkey, new_authorized_pubkey, VoteAuthorize::Voter);
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
let bob_pubkey = Pubkey::new_rand();
let custodian = Pubkey::new_rand();
config.command = WalletCommand::CreateStakeAccount(
bob_pubkey,
bob_keypair,
new_authorized_pubkey,
VoteAuthorize::Voter,
Authorized {
staker: config.keypair.pubkey(),
withdrawer: config.keypair.pubkey(),
},
Lockup { slot: 0, custodian },
10,
);
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
// TODO: Need to add mock GetAccountInfo to mock_rpc_client_request.rs to re-enable the
// DeactivateStake test.
/*
let bob_keypair = Keypair::new();
let vote_pubkey = Pubkey::new_rand();
config.command = WalletCommand::DelegateStake(bob_keypair.into(), vote_pubkey, 100, true);
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
*/
let bob_keypair = Keypair::new();
let stake_pubkey = Pubkey::new_rand();
let to_pubkey = Pubkey::new_rand();
config.command = WalletCommand::WithdrawStake(bob_keypair.into(), to_pubkey, 100);
config.command = WalletCommand::WithdrawStake(stake_pubkey, to_pubkey, 100);
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
let bob_keypair = Keypair::new();
let stake_pubkey = Pubkey::new_rand();
let vote_pubkey = Pubkey::new_rand();
config.command = WalletCommand::DeactivateStake(bob_keypair.into(), vote_pubkey);
config.command = WalletCommand::DeactivateStake(stake_pubkey, vote_pubkey);
let signature = process_command(&config);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
@ -3000,12 +2497,7 @@ mod tests {
);
assert!(process_command(&config).is_err());
config.command = WalletCommand::VoteAuthorize(
bob_pubkey,
Keypair::new(),
bob_pubkey,
VoteAuthorize::Voter,
);
config.command = WalletCommand::VoteAuthorize(bob_pubkey, bob_pubkey, VoteAuthorize::Voter);
assert!(process_command(&config).is_err());
config.command = WalletCommand::GetSlot;

View File

@ -311,8 +311,7 @@ impl RpcClient {
response
.and_then(|account_json| {
let account: Account =
serde_json::from_value(account_json).expect("deserialize account");
let account: Account = serde_json::from_value(account_json)?;
trace!("Response account {:?} {:?}", pubkey, account);
Ok(account)
})

View File

@ -162,8 +162,8 @@ pub(crate) mod tests {
&from_account.pubkey(),
&stake_account_pubkey,
vote_pubkey,
amount,
&Authorized::auto(&stake_account_pubkey),
amount,
),
);
}

View File

@ -464,8 +464,8 @@ impl LocalCluster {
&from_account.pubkey(),
&stake_account_pubkey,
&vote_account_pubkey,
amount,
&StakeAuthorized::auto(&stake_account_pubkey),
amount,
),
client.get_recent_blockhash().unwrap().0,
);

View File

@ -104,5 +104,7 @@ set -x
$solana_cli "${common_args[@]}" \
show-vote-account "$vote_keypair_path"
$solana_cli "${common_args[@]}" \
delegate-stake $maybe_force "$stake_keypair_path" "$vote_keypair_path" "$stake_lamports" lamports
create-stake-account $maybe_force "$stake_keypair_path" lamports
$solana_cli "${common_args[@]}" \
delegate-stake $maybe_force "$stake_keypair_path" "$vote_keypair_path"
$solana_cli "${common_args[@]}" show-stake-account "$stake_keypair_path"

View File

@ -110,9 +110,9 @@ pub enum StakeInstruction {
pub fn create_stake_account_with_lockup(
from_pubkey: &Pubkey,
stake_pubkey: &Pubkey,
lamports: u64,
authorized: &Authorized,
lockup: &Lockup,
lamports: u64,
) -> Vec<Instruction> {
vec![
system_instruction::create_account(
@ -133,15 +133,15 @@ pub fn create_stake_account_with_lockup(
pub fn create_stake_account(
from_pubkey: &Pubkey,
stake_pubkey: &Pubkey,
lamports: u64,
authorized: &Authorized,
lamports: u64,
) -> Vec<Instruction> {
create_stake_account_with_lockup(
from_pubkey,
stake_pubkey,
lamports,
authorized,
&Lockup::default(),
lamports,
)
}
@ -149,11 +149,15 @@ pub fn create_stake_account_and_delegate_stake(
from_pubkey: &Pubkey,
stake_pubkey: &Pubkey,
vote_pubkey: &Pubkey,
lamports: u64,
authorized: &Authorized,
lamports: u64,
) -> Vec<Instruction> {
let mut instructions = create_stake_account(from_pubkey, stake_pubkey, lamports, authorized);
instructions.push(delegate_stake(stake_pubkey, vote_pubkey));
let mut instructions = create_stake_account(from_pubkey, stake_pubkey, authorized, lamports);
instructions.push(delegate_stake(
stake_pubkey,
&authorized.staker,
vote_pubkey,
));
instructions
}
@ -206,32 +210,54 @@ pub fn redeem_vote_credits(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instr
Instruction::new(id(), &StakeInstruction::RedeemVoteCredits, account_metas)
}
pub fn delegate_stake(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction {
let account_metas = vec![
AccountMeta::new(*stake_pubkey, true),
AccountMeta::new_credit_only(*vote_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false),
AccountMeta::new_credit_only(crate::config::id(), false),
];
pub fn delegate_stake(
stake_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
vote_pubkey: &Pubkey,
) -> Instruction {
let account_metas = metas_for_authorized_signer(
stake_pubkey,
authorized_pubkey,
&[
AccountMeta::new_credit_only(*vote_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false),
AccountMeta::new_credit_only(crate::config::id(), false),
],
);
Instruction::new(id(), &StakeInstruction::DelegateStake, account_metas)
}
pub fn withdraw(stake_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Instruction {
let account_metas = vec![
AccountMeta::new(*stake_pubkey, true),
AccountMeta::new_credit_only(*to_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false),
AccountMeta::new_credit_only(sysvar::stake_history::id(), false),
];
pub fn withdraw(
stake_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
to_pubkey: &Pubkey,
lamports: u64,
) -> Instruction {
let account_metas = metas_for_authorized_signer(
stake_pubkey,
authorized_pubkey,
&[
AccountMeta::new_credit_only(*to_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false),
AccountMeta::new_credit_only(sysvar::stake_history::id(), false),
],
);
Instruction::new(id(), &StakeInstruction::Withdraw(lamports), account_metas)
}
pub fn deactivate_stake(stake_pubkey: &Pubkey, vote_pubkey: &Pubkey) -> Instruction {
let account_metas = vec![
AccountMeta::new(*stake_pubkey, true),
AccountMeta::new_credit_only(*vote_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false),
];
pub fn deactivate_stake(
stake_pubkey: &Pubkey,
authorized_pubkey: &Pubkey,
vote_pubkey: &Pubkey,
) -> Instruction {
let account_metas = metas_for_authorized_signer(
stake_pubkey,
authorized_pubkey,
&[
AccountMeta::new_credit_only(*vote_pubkey, false),
AccountMeta::new_credit_only(sysvar::clock::id(), false),
],
);
Instruction::new(id(), &StakeInstruction::Deactivate, account_metas)
}
@ -361,15 +387,28 @@ mod tests {
Err(InstructionError::InvalidAccountData),
);
assert_eq!(
process_instruction(&delegate_stake(&Pubkey::default(), &Pubkey::default())),
process_instruction(&delegate_stake(
&Pubkey::default(),
&Pubkey::default(),
&Pubkey::default()
)),
Err(InstructionError::InvalidAccountData),
);
assert_eq!(
process_instruction(&withdraw(&Pubkey::default(), &Pubkey::new_rand(), 100)),
process_instruction(&withdraw(
&Pubkey::default(),
&Pubkey::default(),
&Pubkey::default(),
100
)),
Err(InstructionError::InvalidAccountData),
);
assert_eq!(
process_instruction(&deactivate_stake(&Pubkey::default(), &Pubkey::default())),
process_instruction(&deactivate_stake(
&Pubkey::default(),
&Pubkey::default(),
&Pubkey::default()
)),
Err(InstructionError::InvalidAccountData),
);
}

View File

@ -101,8 +101,8 @@ fn test_stake_account_delegate() {
&mint_pubkey,
&staker_pubkey,
&vote_pubkey,
1_000_000,
&authorized,
1_000_000,
));
bank_client
.send_message(&[&mint_keypair, &staker_keypair], message)
@ -120,6 +120,7 @@ fn test_stake_account_delegate() {
// Test that we cannot withdraw staked lamports
let message = Message::new_with_payer(
vec![stake_instruction::withdraw(
&staker_pubkey,
&staker_pubkey,
&Pubkey::new_rand(),
1_000_000,
@ -187,6 +188,7 @@ fn test_stake_account_delegate() {
// Deactivate the stake
let message = Message::new_with_payer(
vec![stake_instruction::deactivate_stake(
&staker_pubkey,
&staker_pubkey,
&vote_pubkey,
)],
@ -199,6 +201,7 @@ fn test_stake_account_delegate() {
// Test that we cannot withdraw staked lamports due to cooldown period
let message = Message::new_with_payer(
vec![stake_instruction::withdraw(
&staker_pubkey,
&staker_pubkey,
&Pubkey::new_rand(),
1_000_000,
@ -220,6 +223,7 @@ fn test_stake_account_delegate() {
let message = Message::new_with_payer(
vec![stake_instruction::withdraw(
&staker_pubkey,
&staker_pubkey,
&Pubkey::new_rand(),
1_000_000,
@ -233,6 +237,7 @@ fn test_stake_account_delegate() {
let message = Message::new_with_payer(
vec![stake_instruction::withdraw(
&staker_pubkey,
&staker_pubkey,
&Pubkey::new_rand(),
250_000,
@ -257,6 +262,7 @@ fn test_stake_account_delegate() {
// Test that we can withdraw now
let message = Message::new_with_payer(
vec![stake_instruction::withdraw(
&staker_pubkey,
&staker_pubkey,
&Pubkey::new_rand(),
750_000,