Compare commits
157 Commits
master
...
token-v3.1
Author | SHA1 | Date |
---|---|---|
|
b1629a18f6 | |
|
cef8b7363f | |
|
5afe5c7175 | |
|
0eadd43890 | |
|
e8c97e3cb2 | |
|
d8c062e522 | |
|
e5af52d6e7 | |
|
1beeb9fd21 | |
|
9da22609af | |
|
9342fa39f3 | |
|
df466175ae | |
|
6b82600d05 | |
|
0f9859410f | |
|
c71671cf2a | |
|
a1071cd8c6 | |
|
7b897cf0de | |
|
dc730085de | |
|
5e58890397 | |
|
6903f32221 | |
|
893ab9f2f5 | |
|
f2d4ad4500 | |
|
ba389f9581 | |
|
2625fb006b | |
|
ee4a135e20 | |
|
c0af8bdd0a | |
|
7fe1abeb6b | |
|
59c6bb7072 | |
|
561053730e | |
|
56572e32cb | |
|
2492734068 | |
|
0f82fa7d9c | |
|
1a6df09e89 | |
|
58221fc9ae | |
|
d4bbdf8224 | |
|
41568014eb | |
|
731bb6f57a | |
|
cebb3176b2 | |
|
f3a8fae2f5 | |
|
3709ac60db | |
|
5a9eebd692 | |
|
d3d527e26b | |
|
88eb28625b | |
|
e7360196ae | |
|
0007114121 | |
|
848c580889 | |
|
7f3ccec305 | |
|
1f48b5bc5e | |
|
8a02ab5650 | |
|
7336a7641e | |
|
e402aed8dd | |
|
96100f751b | |
|
6ac82f9caf | |
|
25b51bc2a0 | |
|
f0ad7b3d5f | |
|
2522d937d2 | |
|
d68608c440 | |
|
c6a9446b17 | |
|
0ac20fcddf | |
|
9ca1c6e8f2 | |
|
5a5f129694 | |
|
13689ac2dd | |
|
18d96ffce6 | |
|
f0b1cec4d8 | |
|
cfc6c582ff | |
|
090ecefa46 | |
|
9482f8d4e3 | |
|
1526301d0d | |
|
3045d7b1df | |
|
ea4b7e62fc | |
|
51cc3ed6fe | |
|
9aa238e0b4 | |
|
00eb817073 | |
|
4e1892adba | |
|
3714c70d12 | |
|
a05fd7185d | |
|
cf2fcde121 | |
|
3d04aa0109 | |
|
7320cf404b | |
|
975b524487 | |
|
a539ef021f | |
|
1efc90c5c9 | |
|
0ce5b41b9f | |
|
1f36ca301b | |
|
4068b77f53 | |
|
512497aa39 | |
|
d9fd11a8f3 | |
|
9e4764faf7 | |
|
0ed080a50f | |
|
3dd6767297 | |
|
b99c9b375c | |
|
9c63bc0b06 | |
|
5a357a50df | |
|
0f4f2b8de9 | |
|
60ef11e26b | |
|
5e320ba976 | |
|
636407d7a9 | |
|
11e207cc85 | |
|
14bdbdc3ac | |
|
7d666b86ce | |
|
81ab529311 | |
|
c9a5289aa0 | |
|
ca6d57991a | |
|
99aaab0993 | |
|
1e28a427a4 | |
|
61a53abf6f | |
|
1e47030549 | |
|
9ad4168253 | |
|
dfc5cc5a23 | |
|
c149b0a46e | |
|
804a61e558 | |
|
3613ffe3b0 | |
|
08c4cb530a | |
|
fdba05714d | |
|
53c86493e6 | |
|
40ebfc6917 | |
|
30671aa5b3 | |
|
cf8eeb0720 | |
|
df994bf426 | |
|
6fee08be2f | |
|
a5c4b1e071 | |
|
589da55e29 | |
|
ebc16782bb | |
|
18468b513f | |
|
7a0e5aa14e | |
|
9dd807c893 | |
|
71e5e556c4 | |
|
d3e26d089b | |
|
48a0f81ab6 | |
|
9281b6e828 | |
|
96901b1299 | |
|
b8a773fddd | |
|
4374d8dee4 | |
|
6a52ba7d92 | |
|
24ea32aa48 | |
|
1ff9f789e7 | |
|
529b070e2b | |
|
13f8430b77 | |
|
b581ab3319 | |
|
31d5640706 | |
|
34571b624f | |
|
2b3f71ead5 | |
|
2ef336fd0a | |
|
78ab468781 | |
|
0d5146e30c | |
|
34583bf748 | |
|
fa5c34dc84 | |
|
4d1bb013fb | |
|
3ab19ba514 | |
|
e8f59e42ba | |
|
7f89183c0d | |
|
17dd53d5e8 | |
|
8f325dcd2d | |
|
0e2b08066b | |
|
c01665832a | |
|
ee52f1d499 | |
|
092432f1e1 | |
|
f309df4f35 |
|
@ -10,7 +10,34 @@ on:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
all_github_action_checks:
|
check_non_docs:
|
||||||
|
outputs:
|
||||||
|
run_all_github_action_checks: ${{ steps.check_files.outputs.run_all_github_action_checks }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: check modified files
|
||||||
|
id: check_files
|
||||||
|
run: |
|
||||||
|
echo "========== check paths of modified files =========="
|
||||||
|
echo "::set-output name=run_all_github_action_checks::true"
|
||||||
|
git diff --name-only HEAD^ HEAD > files.txt
|
||||||
|
while IFS= read -r file
|
||||||
|
do
|
||||||
|
if [[ $file != docs/** ]]; then
|
||||||
|
echo "Found modified non-'docs' file(s)"
|
||||||
|
echo "::set-output name=run_all_github_action_checks::false"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done < files.txt
|
||||||
|
|
||||||
|
all_github_action_checks:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: check_non_docs
|
||||||
|
if: needs.check_non_docs.outputs.run_all_github_action_checks == 'true'
|
||||||
steps:
|
steps:
|
||||||
- run: echo "Done"
|
- run: echo "Done"
|
||||||
|
|
|
@ -8,3 +8,5 @@ node_modules
|
||||||
hfuzz_target
|
hfuzz_target
|
||||||
hfuzz_workspace
|
hfuzz_workspace
|
||||||
**/*.so
|
**/*.so
|
||||||
|
**/.DS_Store
|
||||||
|
test-ledger
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,14 +9,15 @@ members = [
|
||||||
"examples/rust/transfer-lamports",
|
"examples/rust/transfer-lamports",
|
||||||
"feature-proposal/program",
|
"feature-proposal/program",
|
||||||
"feature-proposal/cli",
|
"feature-proposal/cli",
|
||||||
|
"governance/program",
|
||||||
"libraries/math",
|
"libraries/math",
|
||||||
"memo/program",
|
"memo/program",
|
||||||
|
"name-service/program",
|
||||||
"record/program",
|
"record/program",
|
||||||
"shared-memory/program",
|
"shared-memory/program",
|
||||||
"stake-pool/cli",
|
"stake-pool/cli",
|
||||||
"stake-pool/program",
|
"stake-pool/program",
|
||||||
"token-lending/program",
|
"token-lending/program",
|
||||||
"token-lending/client",
|
|
||||||
"token-swap/program",
|
"token-swap/program",
|
||||||
"token-swap/program/fuzz",
|
"token-swap/program/fuzz",
|
||||||
"token/cli",
|
"token/cli",
|
||||||
|
@ -29,3 +30,6 @@ exclude = [
|
||||||
"themis/program_ristretto",
|
"themis/program_ristretto",
|
||||||
"token/perf-monitor", # TODO: Rework perf-monitor to use solana-program-test, avoiding the need to link directly with the BPF VM
|
"token/perf-monitor", # TODO: Rework perf-monitor to use solana-program-test, avoiding the need to link directly with the BPF VM
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
split-debuginfo = "unpacked"
|
||||||
|
|
|
@ -12,12 +12,12 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
spl-token = { version = "3.1", path = "../../token/program", features = ["no-entrypoint"] }
|
spl-token = { version = "3.1", path = "../../token/program", features = ["no-entrypoint"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.6.2"
|
solana-program-test = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -13,7 +13,7 @@ test-bpf = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
num-derive = "0.3"
|
num-derive = "0.3"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
spl-token = { version = "3.0", path = "../../token/program", features = [ "no-entrypoint" ] }
|
spl-token = { version = "3.0", path = "../../token/program", features = [ "no-entrypoint" ] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
uint = "0.8"
|
uint = "0.8"
|
||||||
|
@ -21,8 +21,8 @@ arbitrary = { version = "0.4", features = ["derive"], optional = true }
|
||||||
borsh = "0.8.2"
|
borsh = "0.8.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.6.2"
|
solana-program-test = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -4,13 +4,10 @@ set -ex
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
source ./ci/solana-version.sh install
|
source ./ci/solana-version.sh install
|
||||||
|
|
||||||
(cd token/js && npm install)
|
|
||||||
|
|
||||||
cd token-swap/js
|
cd token-swap/js
|
||||||
npm install
|
npm install
|
||||||
npm run lint
|
npm run lint
|
||||||
npm run flow
|
npm run build
|
||||||
npx tsc module.d.ts
|
|
||||||
npm run start-with-test-validator
|
npm run start-with-test-validator
|
||||||
(cd ../../target/deploy && mv spl_token_swap_production.so spl_token_swap.so)
|
(cd ../../target/deploy && mv spl_token_swap_production.so spl_token_swap.so)
|
||||||
SWAP_PROGRAM_OWNER_FEE_ADDRESS="HfoTxFR1Tm6kGmWgYWD6J7YHVy1UwqSULUGVLXkJqaKN" npm run start-with-test-validator
|
SWAP_PROGRAM_OWNER_FEE_ADDRESS="HfoTxFR1Tm6kGmWgYWD6J7YHVy1UwqSULUGVLXkJqaKN" npm run start-with-test-validator
|
||||||
|
|
|
@ -18,13 +18,13 @@
|
||||||
if [[ -n $RUST_STABLE_VERSION ]]; then
|
if [[ -n $RUST_STABLE_VERSION ]]; then
|
||||||
stable_version="$RUST_STABLE_VERSION"
|
stable_version="$RUST_STABLE_VERSION"
|
||||||
else
|
else
|
||||||
stable_version=1.50.0
|
stable_version=1.51.0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
||||||
nightly_version="$RUST_NIGHTLY_VERSION"
|
nightly_version="$RUST_NIGHTLY_VERSION"
|
||||||
else
|
else
|
||||||
nightly_version=2021-02-18
|
nightly_version=2021-04-18
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
if [[ -n $SOLANA_VERSION ]]; then
|
if [[ -n $SOLANA_VERSION ]]; then
|
||||||
solana_version="$SOLANA_VERSION"
|
solana_version="$SOLANA_VERSION"
|
||||||
else
|
else
|
||||||
solana_version=v1.5.15
|
solana_version=v1.6.2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export solana_version="$solana_version"
|
export solana_version="$solana_version"
|
||||||
|
|
|
@ -8,6 +8,7 @@ module.exports = {
|
||||||
"token-lending",
|
"token-lending",
|
||||||
"associated-token-account",
|
"associated-token-account",
|
||||||
"memo",
|
"memo",
|
||||||
|
"name-service",
|
||||||
"shared-memory",
|
"shared-memory",
|
||||||
"stake-pool",
|
"stake-pool",
|
||||||
"feature-proposal",
|
"feature-proposal",
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
title: Name Service
|
||||||
|
---
|
||||||
|
|
||||||
|
A SPL program for issuing and managing ownership of: domain names, Solana Pubkeys, URLs, Twitter handles, ipfs cid's etc..
|
||||||
|
|
||||||
|
This program could be used for dns, pubkey etc lookups via a browser extension
|
||||||
|
for example, the goal is to create an easy way to identify Solana public keys
|
||||||
|
with various links.
|
||||||
|
|
||||||
|
Broader use cases are also imaginable.
|
||||||
|
|
||||||
|
Key points:
|
||||||
|
- A Name is a string that maps to a record (program derived account) which can hold data.
|
||||||
|
- Each name is of a certain class and has a certain owner, both are identified
|
||||||
|
by their pubkeys. The class of a name needs to sign the issuance of it.
|
||||||
|
- A name can have a parent name that is identified by the address of its record.
|
||||||
|
The owner of the parent name (when it exists) needs to sign the issuance of
|
||||||
|
the child name.
|
||||||
|
- The data of a name registry is controlled by the class keypair or, when it is
|
||||||
|
set to `Pubkey::default()`, by the name owner keypair.
|
||||||
|
- Only the owner can delete a name registry.
|
||||||
|
|
||||||
|
|
||||||
|
Remarks and use cases:
|
||||||
|
- Domain name declarations: One could arbitrarily set-up a class that we can call
|
||||||
|
Top-Level-Domain names. Names in this class can only be issued with the
|
||||||
|
permission of the class keypair, ie the administrator, who can enforce that
|
||||||
|
TLD names are of the type `".something"`. From then on one could create and
|
||||||
|
own the TLD `".sol"` and create a class of ".sol" sub-domains, administrating
|
||||||
|
the issuance of the `"something.sol"` sub-domains that way (by setting the
|
||||||
|
parent name to the address of the `".sol"` registry).
|
||||||
|
|
||||||
|
An off-chain browser extension could then, similarly to DNS, parse the user SPL
|
||||||
|
name service URL input and descend the chain of names, verifying that the names
|
||||||
|
exist with the correct parenthood, and finally use the data of the last child
|
||||||
|
name (or also a combination of the parents data) in order to resolve this call
|
||||||
|
towards a real DNS URL or any kind of data.
|
||||||
|
|
||||||
|
Although the ownership and class system makes the administration a given class
|
||||||
|
centralized, the creation of new classes is permissionless and as a class owner
|
||||||
|
any kind of decentralized governance signing program could be used.
|
||||||
|
|
||||||
|
- Twitter handles can be added as names of one specific name class. The class
|
||||||
|
authority of will therefore hold the right to add a Twitter handle name. This
|
||||||
|
enables the verification of Twitter accounts for example by asking the user to
|
||||||
|
tweet his pubkey or a signed message. A bot that holds the private issuing
|
||||||
|
authority key can then sign the Create instruction (with a metadata_authority
|
||||||
|
that is the tweeted pubkey) and send it back to the user who will then submit
|
||||||
|
it to the program.
|
||||||
|
In this case the class will still be able to control the data of the name registry, and not the user for example.
|
||||||
|
|
||||||
|
Therefore, another way of using this program would be to create a name
|
||||||
|
(`"verified-twitter-handles"` for example) with the `Pubkey::default()` class
|
||||||
|
and with the owner being the authority. That way verified Twitter names could be
|
||||||
|
issued as child names of this parent by the owner, leaving the user as being
|
||||||
|
able to modify the data of his Twitter name registry.
|
|
@ -3,7 +3,7 @@ title: Stake Pool Program
|
||||||
---
|
---
|
||||||
|
|
||||||
A program for pooling together SOL to be staked by an off-chain agent running
|
A program for pooling together SOL to be staked by an off-chain agent running
|
||||||
a Delegation bot which redistributes the stakes across the network and tries
|
a Delegation Bot which redistributes the stakes across the network and tries
|
||||||
to maximize censorship resistance and rewards.
|
to maximize censorship resistance and rewards.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
@ -14,7 +14,7 @@ inflation rate, total number of SOL staked on the network, and an individual
|
||||||
validator’s uptime and commission (fee).
|
validator’s uptime and commission (fee).
|
||||||
|
|
||||||
Stake pools are an alternative method of earning staking rewards. This on-chain
|
Stake pools are an alternative method of earning staking rewards. This on-chain
|
||||||
program pools together SOL to be staked by a manager, allowing SOL holders to
|
program pools together SOL to be staked by a staker, allowing SOL holders to
|
||||||
stake and earn rewards without managing stakes.
|
stake and earn rewards without managing stakes.
|
||||||
|
|
||||||
Additional information regarding staking and stake programming is available at:
|
Additional information regarding staking and stake programming is available at:
|
||||||
|
@ -24,16 +24,18 @@ Additional information regarding staking and stake programming is available at:
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
This document is intended for stake pool managers who want to create or manage
|
This document is intended for the main actors of the stake pool system:
|
||||||
stake pools, and users who want to provide staked SOL into an existing stake
|
|
||||||
pool.
|
* manager: creates and manages the stake pool, earns fees, can update the fee, staker, and manager
|
||||||
|
* staker: adds and removes validators to the pool, rebalances stake among validators
|
||||||
|
* user: provides staked SOL into an existing stake pool
|
||||||
|
|
||||||
In its current iteration, the stake pool only processes totally active stakes.
|
In its current iteration, the stake pool only processes totally active stakes.
|
||||||
Deposits must come from fully active stakes, and withdrawals return a fully
|
Deposits must come from fully active stakes, and withdrawals return a fully
|
||||||
active stake account.
|
active stake account.
|
||||||
|
|
||||||
This means that stake pool managers and users must be comfortable with creating
|
This means that stake pool managers, stakers, and users must be comfortable with
|
||||||
and delegating stakes, which are more advanced operations than sending and
|
creating and delegating stakes, which are more advanced operations than sending and
|
||||||
receiving SPL tokens and SOL. Additional information on stake operations are
|
receiving SPL tokens and SOL. Additional information on stake operations are
|
||||||
available at:
|
available at:
|
||||||
|
|
||||||
|
@ -46,27 +48,28 @@ like [Token Swap](token-swap.md).
|
||||||
|
|
||||||
## Operation
|
## Operation
|
||||||
|
|
||||||
A stake pool manager creates a stake pool and includes validators that will
|
A stake pool manager creates a stake pool, and the staker includes validators that will
|
||||||
receive delegations from the pool by creating "validator stake accounts" and
|
receive delegations from the pool by creating "validator stake accounts" and
|
||||||
activating a delegation on them. Once a validator stake account's delegation is
|
activating a delegation on them. Once a validator stake account's delegation is
|
||||||
active, the stake pool manager adds it to the stake pool.
|
active, the staker adds it to the stake pool.
|
||||||
|
|
||||||
At this point, users can participate with deposits. They must delegate a stake
|
At this point, users can participate with deposits. They must delegate a stake
|
||||||
account to the one of the validators in the stake pool. Once it's active, the
|
account to the one of the validators in the stake pool. Once it's active, the
|
||||||
user can deposit their stake into the pool in exchange for SPL staking derivatives
|
user can deposit their stake into the pool in exchange for SPL staking derivatives
|
||||||
representing their fractional ownership in pool. A percentage of the user's
|
representing their fractional ownership in pool. A percentage of the rewards
|
||||||
deposit goes to the pool manager as a fee.
|
earned by the pool goes to the pool manager as a fee.
|
||||||
|
|
||||||
Over time, as the stake pool accrues staking rewards, the user's fractional
|
Over time, as the stakes in the stake pool accrue staking rewards, the user's fractional
|
||||||
ownership will be worth more than their initial deposit. Whenever the user chooses,
|
ownership will be worth more than their initial deposit. Whenever the user chooses,
|
||||||
they can withdraw their SPL staking derivatives in exchange for an activated stake.
|
they can withdraw their SPL staking derivatives in exchange for an activated stake.
|
||||||
|
|
||||||
The stake pool manager can add and remove validators, or rebalance the pool by
|
The stake pool staker can add and remove validators, or rebalance the pool by
|
||||||
withdrawing stakes from the pool, deactivating them, reactivating them on another
|
decreasing the stake on a validator, waiting an epoch to move it into the stake
|
||||||
validator, then depositing back into the pool.
|
pool's reserve account, then increasing the stake on another validator.
|
||||||
|
|
||||||
These manager operations require SPL staking derivatives and staked SOL, so the
|
The staker operation to add a new validator requires roughly 1.003 SOL to create
|
||||||
stake pool manager will need liquidity on hand to properly manage the pool.
|
the stake account on a validator, so the stake pool staker will need liquidity
|
||||||
|
on hand to fully manage the pool stakes.
|
||||||
|
|
||||||
## Background
|
## Background
|
||||||
|
|
||||||
|
@ -130,32 +133,105 @@ Hardware Wallet URL (See [URL spec](https://docs.solana.com/wallet-guide/hardwar
|
||||||
solana config set --keypair usb://ledger/
|
solana config set --keypair usb://ledger/
|
||||||
```
|
```
|
||||||
|
|
||||||
### Stake Pool Administrator Examples
|
#### Run Locally
|
||||||
|
|
||||||
|
If you would like to test a stake pool locally without having to wait for stakes
|
||||||
|
to activate and deactivate, you can run the stake pool locally using the
|
||||||
|
`solana-test-validator` tool with shorter epochs, and pulling the current program
|
||||||
|
from devnet, testnet, or mainnet.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ solana-test-validator -c poo1B9L9nR3CrcaziKVYVpRX6A9Y1LAXYasjjfCbApj --url devnet --slots-per-epoch 32
|
||||||
|
$ solana config set --url http://127.0.0.1:8899
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stake Pool Manager Examples
|
||||||
|
|
||||||
#### Create a stake pool
|
#### Create a stake pool
|
||||||
|
|
||||||
The pool administrator manages the stake accounts in a stake pool, and in exchange
|
The stake pool manager controls the stake pool from a high level, and in exchange
|
||||||
receives a fee in the form of SPL token staking derivatives. The administrator
|
receives a fee in the form of SPL token staking derivatives. The manager
|
||||||
sets the fee on creation. Let's create a pool with a 3% fee:
|
sets the fee on creation. Let's create a pool with a 3% fee and a maximum of 1000
|
||||||
|
validator stake accounts:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool create-pool --fee-numerator 3 --fee-denominator 100
|
$ spl-stake-pool create-pool --fee-numerator 3 --fee-denominator 100 --max-validators 1000
|
||||||
Creating mint Gmk71cM7j2RMorRsQrsyysM4HsByQx5PuDGtDdqGLWCS
|
Creating reserve stake 33Hg3bvYrAwfqCzTMjAWZNAWC6H96qJNEdzGamfFjG4J
|
||||||
Creating pool fee collection account 3xvXPfQi2SaTkqPV9A7BQwh4GyTe2ZPasfoaCBCnTAJ5
|
Creating mint D5yiK1tE1yAXBnrV9ZrSUJCw8WiQctZ8ekbv1U6ATVZ
|
||||||
Creating stake pool 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
Creating pool fee collection account 5gpuSdutGY98KKbgmR5CfLK7toFcQD69JzKDwseegzXE
|
||||||
Signature: 5HdDoPssqwyLjt2QvhRbnSATZqFLGKha92zMuJiBUpKeKYKGURRV41N5ydCQxqnFjCud3xv85Z6ghErppNJzaYM8
|
Signature: 2dvCtHMcqxibckhvVgFQeFCRb7VcHbuFLRf71Aqd9PtzFzdbG3gAkNpxYznfpKDx2vTRrVtwW81sZAx5U3Frb5Uu
|
||||||
|
Creating stake pool EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||||
|
Signature: 2kYDVyJp8FVrLmEZyW9ivMYcXEsgWm4hFyhp5omxVtonjhYG6WS1S85sPTCdsQWe3idof6ZqsY8F3oaMXwrEkAYK
|
||||||
```
|
```
|
||||||
|
|
||||||
The unique stake pool identifier is `3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC`.
|
The unique stake pool identifier is `EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1`.
|
||||||
|
|
||||||
The identifier for the SPL token for staking derivatives is
|
The identifier for the SPL token for staking derivatives is
|
||||||
`Gmk71cM7j2RMorRsQrsyysM4HsByQx5PuDGtDdqGLWCS`. The stake pool has full control
|
`D5yiK1tE1yAXBnrV9ZrSUJCw8WiQctZ8ekbv1U6ATVZ`. The stake pool has full control
|
||||||
over the mint.
|
over the mint.
|
||||||
|
|
||||||
The pool creator's fee account identifier is
|
The pool creator's fee account identifier is
|
||||||
`3xvXPfQi2SaTkqPV9A7BQwh4GyTe2ZPasfoaCBCnTAJ5`. When users deposit warmed up
|
`5gpuSdutGY98KKbgmR5CfLK7toFcQD69JzKDwseegzXE`. Every epoch, as stake accounts
|
||||||
stake accounts into the stake pool, the program will transfer 3% of their
|
in the stake pool earn rewards, the program will mint SPL token staking derivatives
|
||||||
contribution into this account in the form of SPL token staking derivatives.
|
equal to 3% of the gains on that epoch into this account. If no gains were observed,
|
||||||
|
nothing will be deposited.
|
||||||
|
|
||||||
|
The reserve stake account identifier is `33Hg3bvYrAwfqCzTMjAWZNAWC6H96qJNEdzGamfFjG4J`.
|
||||||
|
This account holds onto additional stake used when rebalancing between validators.
|
||||||
|
|
||||||
|
For a stake pool with 1000 validators, the cost to create a stake pool is less
|
||||||
|
than 0.5 SOL.
|
||||||
|
|
||||||
|
#### Set manager
|
||||||
|
|
||||||
|
The stake pool manager may pass their administrator privileges to another account.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ spl-stake-pool set-manager EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 --new-manager 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||||
|
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
|
||||||
|
```
|
||||||
|
|
||||||
|
At the same time, they may also change the SPL token account that receives fees
|
||||||
|
every epoch. The mint for the provided token account must be the SPL token mint,
|
||||||
|
`D5yiK1tE1yAXBnrV9ZrSUJCw8WiQctZ8ekbv1U6ATVZ` in our example.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ spl-stake-pool set-manager EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 --new-fee-receiver HoCsh97wRxRXVjtG7dyfsXSwH9VxdDzC7GvAsBE1eqJz
|
||||||
|
Signature: 4aK8yzYvPBkP4PyuXTcCm529kjEH6tTt4ixc5D5ZyCrHwc4pvxAHj6wcr4cpAE1e3LddE87J1GLD466aiifcXoAY
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Set fee
|
||||||
|
|
||||||
|
The stake pool manager may update the fee assessed every epoch, passing the
|
||||||
|
numerator and denominator for the fraction that make up the fee. For a fee of
|
||||||
|
10%, they could run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ spl-stake-pool set-fee EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 10 100
|
||||||
|
Signature: 5yPXfVj5cbKBfZiEVi2UR5bXzVDuc2c3ruBwSjkAqpvxPHigwGHiS1mXQVE4qwok5moMWT5RNYAMvkE9bnfQ1i93
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Set staker
|
||||||
|
|
||||||
|
In order to manage the stake accounts, the stake pool manager or
|
||||||
|
staker can set the staker authority of the stake pool's managed accounts.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ spl-stake-pool set-staker EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||||
|
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, the new staker can perform any normal stake pool operations, including
|
||||||
|
adding and removing validators and rebalancing stake.
|
||||||
|
|
||||||
|
Important security note: the stake pool program only gives staking authority to
|
||||||
|
the pool staker and always retains withdraw authority. Therefore, a malicious
|
||||||
|
stake pool staker cannot steal funds from the stake pool.
|
||||||
|
|
||||||
|
Note: to avoid "disturbing the manager", the staker can also reassign their stake
|
||||||
|
authority.
|
||||||
|
|
||||||
|
### Stake Pool Staker Examples
|
||||||
|
|
||||||
#### Create a validator stake account
|
#### Create a validator stake account
|
||||||
|
|
||||||
|
@ -170,7 +246,7 @@ lists, we choose some validators at random and start with identity
|
||||||
delegated to that vote account.
|
delegated to that vote account.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool create-validator-stake 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
$ spl-stake-pool create-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
||||||
Creating stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
Creating stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||||
Signature: 4pA2WKT6d2wkXEtSpiQswv22WyoFad2KX6FdPEzwBiEquvaUBEtzenys5Jh1ABPCh7yc4w8kzqMRRCwDj6ZSUV1K
|
Signature: 4pA2WKT6d2wkXEtSpiQswv22WyoFad2KX6FdPEzwBiEquvaUBEtzenys5Jh1ABPCh7yc4w8kzqMRRCwDj6ZSUV1K
|
||||||
```
|
```
|
||||||
|
@ -179,13 +255,13 @@ In order to maximize censorship resistance, we want to distribute our SOL to as
|
||||||
many validators as possible, so let's add a few more.
|
many validators as possible, so let's add a few more.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool create-validator-stake 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz
|
$ spl-stake-pool create-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz
|
||||||
Creating stake account E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie
|
Creating stake account E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie
|
||||||
Signature: 4pyRZzjsWG7jP3GRZeZCo2Eb2TPjHM4kAYRFMivimme6HAee1nhzoNJBe3VSt2sv7acp5fwT7J8omBM8o3niY8gu
|
Signature: 4pyRZzjsWG7jP3GRZeZCo2Eb2TPjHM4kAYRFMivimme6HAee1nhzoNJBe3VSt2sv7acp5fwT7J8omBM8o3niY8gu
|
||||||
$ spl-stake-pool create-validator-stake 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
$ spl-stake-pool create-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
||||||
Creating stake account CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E
|
Creating stake account CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E
|
||||||
Signature: 4ZUdZzUARgUCPuY8nVsJbN6vRDbVX8sYAQGYYXj2YVvjoJ2oevq2H8uzrhYApe419uoP7QYukqNstiti5p5DDukN
|
Signature: 4ZUdZzUARgUCPuY8nVsJbN6vRDbVX8sYAQGYYXj2YVvjoJ2oevq2H8uzrhYApe419uoP7QYukqNstiti5p5DDukN
|
||||||
$ spl-stake-pool create-validator-stake 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm
|
$ spl-stake-pool create-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm
|
||||||
Creating stake account FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13
|
Creating stake account FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13
|
||||||
Signature: yQqXCbuA66wQsHtkziNg3XadfZF5aCmvjfentwbZJnSPeEjJwPka3M1QY5GmR1efprptqaePn71BTMSLscX8DLr
|
Signature: yQqXCbuA66wQsHtkziNg3XadfZF5aCmvjfentwbZJnSPeEjJwPka3M1QY5GmR1efprptqaePn71BTMSLscX8DLr
|
||||||
```
|
```
|
||||||
|
@ -235,22 +311,21 @@ We created new validator stake accounts in the last step and staked them. Once
|
||||||
the stake activates, we can add them to the stake pool.
|
the stake activates, we can add them to the stake pool.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool add-validator 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
$ spl-stake-pool add-validator EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
||||||
Creating account to receive tokens Gu8xqzYFg2sPHWHhUivKNBeF9uikiauihLs9hLzziKu7
|
|
||||||
Signature: 3N1K89rGV9gWueTTrPGTDBwKAp8BikQhKHMFoREw98Q1piXFeZSSxqfnRQexrfAZQfrpYH9qwsaPWRruwkVeBivV
|
Signature: 3N1K89rGV9gWueTTrPGTDBwKAp8BikQhKHMFoREw98Q1piXFeZSSxqfnRQexrfAZQfrpYH9qwsaPWRruwkVeBivV
|
||||||
```
|
```
|
||||||
|
|
||||||
Users can start depositing their activated stakes into the stake pool, as
|
Users can start depositing their activated stakes into the stake pool, as
|
||||||
long as they are delegated to the same vote account, which was
|
long as they are delegated to the same vote account, which was
|
||||||
`FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13` in this example. You can also
|
`FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN` in this example. You can also
|
||||||
double-check that at any time using the Solana command-line utility.
|
double-check that at any time using the Solana command-line utility.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ solana stake-account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
$ solana stake-account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||||
Balance: 0.002282881 SOL
|
Balance: 0.002282881 SOL
|
||||||
Rent Exempt Reserve: 0.00228288 SOL
|
Rent Exempt Reserve: 0.00228288 SOL
|
||||||
Delegated Stake: 0.000000001 SOL
|
Delegated Stake: 1.000000000 SOL
|
||||||
Active Stake: 0.000000001 SOL
|
Active Stake: 1.000000000 SOL
|
||||||
Activating Stake: 0 SOL
|
Activating Stake: 0 SOL
|
||||||
Stake activates starting from epoch: 161
|
Stake activates starting from epoch: 161
|
||||||
Delegated Vote Account Address: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
Delegated Vote Account Address: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
||||||
|
@ -260,26 +335,31 @@ Withdraw Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||||
|
|
||||||
#### Remove validator stake account
|
#### Remove validator stake account
|
||||||
|
|
||||||
If the stake pool manager wants to stop delegating to a vote account, they can
|
If the stake pool staker wants to stop delegating to a vote account, they can
|
||||||
totally remove the validator stake account from the stake pool by providing
|
totally remove the validator stake account from the stake pool.
|
||||||
staking derivatives, just like `withdraw`.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool remove-validator 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E --withdraw-from 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
$ spl-stake-pool remove-validator EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
||||||
Signature: 5rrQ3xhDWyiPkUTAQkNAeq31n6sMf1xsg2x9hVY8Vj1NonwBnhxuTv87nADLkwC8Xzc4CGTNCTX2Vph9esWnXk2d
|
Signature: 5rrQ3xhDWyiPkUTAQkNAeq31n6sMf1xsg2x9hVY8Vj1NonwBnhxuTv87nADLkwC8Xzc4CGTNCTX2Vph9esWnXk2d
|
||||||
```
|
```
|
||||||
|
|
||||||
The difference with `withdraw` is that the validator stake account is totally
|
The difference with `withdraw` is that the validator stake account is totally
|
||||||
removed from the stake pool and now belongs to the administrator.
|
removed from the stake pool and now belongs to the administrator. The authority
|
||||||
|
for the withdrawn stake account can also be specified using the `--new-authority` flag:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ spl-stake-pool remove-validator EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G --new-authority 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||||
|
Signature: 5rrQ3xhDWyiPkUTAQkNAeq31n6sMf1xsg2x9hVY8Vj1NonwBnhxuTv87nADLkwC8Xzc4CGTNCTX2Vph9esWnXk2d
|
||||||
|
```
|
||||||
|
|
||||||
We can check the removed stake account:
|
We can check the removed stake account:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ solana stake-account CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E
|
$ solana stake-account CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E
|
||||||
Balance: 1.002282881 SOL
|
Balance: 1.002282880 SOL
|
||||||
Rent Exempt Reserve: 0.00228288 SOL
|
Rent Exempt Reserve: 0.00228288 SOL
|
||||||
Delegated Stake: 1.000000001 SOL
|
Delegated Stake: 1.000000000 SOL
|
||||||
Active Stake: 1.000000001 SOL
|
Active Stake: 1.000000000 SOL
|
||||||
Delegated Vote Account Address: AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
Delegated Vote Account Address: AUCzCaGAGjL3uyjFBtJs7KuJcgQWvNZu1Z2S9G3pw77G
|
||||||
Stake Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
Stake Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||||
Withdraw Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
Withdraw Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||||
|
@ -291,7 +371,7 @@ removal of staked SOL from the pool.
|
||||||
We can also double-check that the stake pool no longer shows the stake account:
|
We can also double-check that the stake pool no longer shows the stake account:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||||
Pubkey: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎1.002282881
|
Pubkey: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎1.002282881
|
||||||
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎3.410872673
|
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎3.410872673
|
||||||
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎11.436803652
|
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎11.436803652
|
||||||
|
@ -300,14 +380,14 @@ Total: ◎15.849959206
|
||||||
|
|
||||||
#### Rebalance the stake pool
|
#### Rebalance the stake pool
|
||||||
|
|
||||||
As time goes on, deposits and withdrawals will happen to all of the stake accounts
|
As time goes on, users will deposit to and withdraw from all of the stake accounts
|
||||||
managed by the pool, and the stake pool manager may want to rebalance the stakes.
|
managed by the pool, and the stake pool staker may want to rebalance the stakes.
|
||||||
|
|
||||||
For example, let's say the manager wants the same delegation to every validator
|
For example, let's say the staker wants the same delegation to every validator
|
||||||
in the pool. When they look at the state of the pool, they see:
|
in the pool. When they look at the state of the pool, they see:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||||
Pubkey: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎1.002282881
|
Pubkey: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎1.002282881
|
||||||
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎3.410872673
|
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎3.410872673
|
||||||
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎11.436803652
|
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎11.436803652
|
||||||
|
@ -315,75 +395,63 @@ Total: ◎15.849959206
|
||||||
```
|
```
|
||||||
|
|
||||||
This isn't great! The last stake account, `E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie`
|
This isn't great! The last stake account, `E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie`
|
||||||
has too much allocated. For their strategy, the manager wants the `15.849959206`
|
has too much allocated. For their strategy, the staker wants the `15.849959206`
|
||||||
SOL to be distributed evenly, meaning around `5.283319735` in each account. They need
|
SOL to be distributed evenly, meaning around `5.283319735` in each account. They need
|
||||||
to move `4.281036854` to `FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13` and
|
to move `4.281036854` to `FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13` and
|
||||||
`1.872447062` to `FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN`.
|
`1.872447062` to `FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN`.
|
||||||
|
|
||||||
First, they need to withdraw a total of `6.153483916` from
|
##### Decrease validator stake
|
||||||
`E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie`. Using the `spl-token` utility,
|
|
||||||
let's check the total supply of pool tokens:
|
First, they need to decrease the amount on stake account
|
||||||
|
`E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie`, delegated to
|
||||||
|
`HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz`, by total of `6.153483916` SOL.
|
||||||
|
|
||||||
|
They decrease that amount of SOL:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-token supply Gmk71cM7j2RMorRsQrsyysM4HsByQx5PuDGtDdqGLWCS
|
$ spl-stake-pool decrease-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz 6.153483916
|
||||||
0.034692168
|
Signature: ZpQGwT85rJ8Y9afdkXhKo3TVv4xgTz741mmZj2vW7mihYseAkFsazWxza2y8eNGY4HDJm15c1cStwyiQzaM3RpH
|
||||||
```
|
```
|
||||||
|
|
||||||
Given a total pool token supply of `0.034692168` and total staked SOL amount of
|
Internally, this instruction splits and deactivates 6.153483916 SOL from the
|
||||||
`15.849959206`, let's calculate how many pool tokens to withdraw from the pool:
|
validator stake account `E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie` into a
|
||||||
|
transient stake account, owned and managed entirely by the stake pool.
|
||||||
|
|
||||||
```
|
Once the stake is deactivated during the next epoch, the `update` command will
|
||||||
sol_to_withdraw * total_pool_tokens / total_sol_staked = pool_tokens_to_withdraw
|
automatically merge the transient stake account into a reserve stake account,
|
||||||
6.153483916 * 0.034692168 / 15.849959206 ~ 0.013468659
|
also entirely owned and managed by the stake pool.
|
||||||
```
|
|
||||||
|
|
||||||
They withdraw that amount of pool tokens:
|
##### Increase validator stake
|
||||||
|
|
||||||
|
Now that the reserve stake account has enough to perform the rebalance, the staker
|
||||||
|
can increase the stake on the two other validators,
|
||||||
|
`8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm` and
|
||||||
|
`2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3`.
|
||||||
|
|
||||||
|
They add 4.281036854 SOL to `8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool withdraw 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --amount 0.013468659 --withdraw-from 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
$ spl-stake-pool increase-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm 4.281036854
|
||||||
Withdrawing from account E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie, amount ◎6.153483855, 0.013468659 pool tokens
|
Signature: 3GJACzjUGLPjcd9RLUW86AfBLWKapZRkxnEMc2yHT6erYtcKBgCapzyrVH6VN8Utxj7e2mtvzcigwLm6ZafXyTMw
|
||||||
Creating account to receive stake 8ykyY7maA9HUfUphZHBkhsnydY5gFfyHFSfxCA7imqrk
|
|
||||||
Signature: z8a5ZRfWdj8Fcsr3ttCJ731wFKyhZNcqoKEdV1RBCkzr3tHGQNCC56qvRVJ6oxyCVDqWZ3KL1Bkyn3sDpjYPDku
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Because of rounding in the calculation a few lines above, it looks like we receive
|
And they add 1.872447062 SOL to `2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3`:
|
||||||
less than we should. If we play that back the other way, we'll see that all is well:
|
|
||||||
|
|
||||||
```
|
|
||||||
pool_tokens_to_withdraw * total_sol_staked / total_pool_tokens = sol_to_withdraw
|
|
||||||
0.013468659 * 15.849959206 / 0.034692168 ~ 6.153483855
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, they deactivate the new received stake:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ solana deactivate-stake 8ykyY7maA9HUfUphZHBkhsnydY5gFfyHFSfxCA7imqrk
|
$ spl-stake-pool increase-validator-stake EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 1.872447062
|
||||||
Signature: 4SuwZK5JvYkYVkM5yfu2x8x6iou6558teMwzphGECLmstMVoWbSvngUH48Ra24PrxtgUDyVDA8SXYS1qMyx3fjMj
|
Signature: 4zaKYu3MQ3as8reLbuHKaXN8FNaHvpHuiZtsJeARo67UKMo6wUUoWE88Fy8N4EYQYicuwULTNffcUD3a9jY88PoU
|
||||||
```
|
```
|
||||||
|
|
||||||
Once the stake is deactivated during the next epoch, they split the stake
|
Internally, this instruction also uses transient stake accounts. This time, the
|
||||||
and activate it on the other two validator vote accounts. For brevity, those
|
stake pool splits from the reserve stake, into the transient stake account,
|
||||||
commands are omitted.
|
then activates it to the appropriate validator.
|
||||||
|
|
||||||
Eventually, we are left with stake account `4zppED2kFodUS2hBf8Fzeepu6yZ6QuyeNPBXCT9VU6fK`
|
One to two epochs later, once the transient stakes activate, the `update` command
|
||||||
with `4.281036854` delegated to `8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm`
|
automatically merges the transient stakes into the validator stake account, leaving
|
||||||
and stake account `GCJnuFGCDzaToPwJtG5GiK4g3DJBfuhQy6388NyGcfwf` with `1.872447062`
|
a fully rebalanced stake pool:
|
||||||
delegated to `2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3`.
|
|
||||||
|
|
||||||
Once the new stakes are ready, the manager deposits them back into the stake pool:
|
|
||||||
```sh
|
|
||||||
$ spl-stake-pool deposit 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC GCJnuFGCDzaToPwJtG5GiK4g3DJBfuhQy6388NyGcfwf --token-receiver 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
|
||||||
Depositing into stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
|
||||||
Signature: jKsdEr3zxF2zZs78rmrP3PmQiTwE7v15ieEuxp4db1VQe9owXVGM8nM3dJqVRHXPsS4frQW4gJ6xBfTTk2HvKDX
|
|
||||||
$ spl-stake-pool deposit 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 4zppED2kFodUS2hBf8Fzeepu6yZ6QuyeNPBXCT9VU6fK --token-receiver 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
|
||||||
Depositing into stake account FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13
|
|
||||||
Signature: 3JXvTvea6F4Epd2krSxnTRZPB4gLZ8GqisFE58Z4ocV92fDN1HRMVPoPhJtYcfuF12vyQZUueKwVmkvL6Wgf2evc
|
|
||||||
```
|
|
||||||
|
|
||||||
Leaving them with a rebalanced stake pool!
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||||
Pubkey: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎5.283340235
|
Pubkey: FhFft7ArhZZkh6q4ir1JZMYFgXdH6wkT5M5nmDDb1Q13 Vote: 8r1f8mwrUiYdg2Rx9sxTh4M3UAUcCBBrmRA3nxk3Z6Lm ◎5.283340235
|
||||||
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎5.283612231
|
Pubkey: FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN Vote: 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3 ◎5.283612231
|
||||||
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎5.284317422
|
Pubkey: E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie Vote: HJiC8iJ4Sj846SswQuauFJK93UvV6zp3c2T6jzGqzhhz ◎5.284317422
|
||||||
|
@ -391,33 +459,7 @@ Total: ◎15.851269888
|
||||||
```
|
```
|
||||||
|
|
||||||
Due to staking rewards that accrued during the rebalancing process, the pool is
|
Due to staking rewards that accrued during the rebalancing process, the pool is
|
||||||
not prefectly balanced. This is completely normal.
|
not perfectly balanced. This is completely normal.
|
||||||
|
|
||||||
#### Set staking authority
|
|
||||||
|
|
||||||
In order to manage the stake accounts more directly, the stake pool owner can
|
|
||||||
set the stake authority of the stake pool's managed accounts.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ spl-stake-pool set-staking-auth 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --stake-account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN --new-staker 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
|
||||||
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, the new staking authority can perform any normal staking operations,
|
|
||||||
including deactivating or re-staking.
|
|
||||||
|
|
||||||
Important security note: the stake pool program only gives staking authority to
|
|
||||||
the pool owner and always retains withdraw authority. Therefore, a malicious
|
|
||||||
stake pool manager cannot steal funds from the stake pool.
|
|
||||||
|
|
||||||
#### Set owner
|
|
||||||
|
|
||||||
The stake pool owner may pass their administrator privileges to another account.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ spl-stake-pool 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --new-owner 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
|
||||||
Signature: 39N5gkaqXuWm6JPEUWfenKXeG4nSa71p7iHb9zurvdZcsWmbjdmSXwLVYfhAVHWucTY77sJ8SkUNpVpVAhe4eZ53
|
|
||||||
```
|
|
||||||
|
|
||||||
### User Examples
|
### User Examples
|
||||||
|
|
||||||
|
@ -429,7 +471,7 @@ command-line utility has a special instruction for finding out which vote
|
||||||
accounts are already associated with the stake pool.
|
accounts are already associated with the stake pool.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||||
CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E 1.002282880 SOL
|
CrStLEWfme37kDc3nubK9HsmWR5dsuVUuqEKqTR4Mc5E 1.002282880 SOL
|
||||||
E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie 1.002282880 SOL
|
E5KBATUd21Dnjnh5sGFw5ngp9kdVXCcAAYMRe2WsVXie 1.002282880 SOL
|
||||||
FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN 1.002282880 SOL
|
FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN 1.002282880 SOL
|
||||||
|
@ -441,13 +483,13 @@ If the manager has recently created the stake pool, and there are no stake
|
||||||
accounts present yet, the command-line utility will inform us.
|
accounts present yet, the command-line utility will inform us.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool list 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
$ spl-stake-pool list EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||||
No accounts found.
|
No accounts found.
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Deposit stake
|
#### Deposit stake
|
||||||
|
|
||||||
Stake pools only accept deposits from fully staked accounts, so we must first
|
Stake pools only accept deposits from active accounts, so we must first
|
||||||
create stake accounts and delegate them to one of the validators managed by the
|
create stake accounts and delegate them to one of the validators managed by the
|
||||||
stake pool. Using the `list` command from the previous section, we see that
|
stake pool. Using the `list` command from the previous section, we see that
|
||||||
`2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3` is a valid vote account, so let's
|
`2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3` is a valid vote account, so let's
|
||||||
|
@ -473,17 +515,19 @@ Two epochs later, when the stake is fully active and has received one epoch of
|
||||||
rewards, we can deposit the stake into the stake pool.
|
rewards, we can deposit the stake into the stake pool.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool deposit 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 4F4AYKZbNtDnu7uQey2Vkz9VgkVtLE6XWLezYjc9yxZa
|
$ spl-stake-pool deposit EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 4F4AYKZbNtDnu7uQey2Vkz9VgkVtLE6XWLezYjc9yxZa
|
||||||
Depositing into stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
Depositing into stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||||
Creating account to receive tokens 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
Creating account to receive tokens 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||||
Signature: 4AESGZzqBVfj5xQnMiPWAwzJnAtQDRFK1Ha6jqKKTs46Zm5fw3LqgU1mRAT6CKTywVfFMHZCLm1hcQNScSMwVvjQ
|
Signature: 4AESGZzqBVfj5xQnMiPWAwzJnAtQDRFK1Ha6jqKKTs46Zm5fw3LqgU1mRAT6CKTywVfFMHZCLm1hcQNScSMwVvjQ
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The CLI will default to using the fee payer's
|
||||||
|
[Associated Token Account](associated-token-account.md) for stake pool tokens.
|
||||||
Alternatively, you can create an SPL token account yourself and pass it as the
|
Alternatively, you can create an SPL token account yourself and pass it as the
|
||||||
`token-receiver` for the command.
|
`token-receiver` for the command.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool deposit 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC 4F4AYKZbNtDnu7uQey2Vkz9VgkVtLE6XWLezYjc9yxZa --token-receiver 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
$ spl-stake-pool deposit EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 4F4AYKZbNtDnu7uQey2Vkz9VgkVtLE6XWLezYjc9yxZa --token-receiver 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||||
Depositing into stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
Depositing into stake account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN
|
||||||
Signature: 4AESGZzqBVfj5xQnMiPWAwzJnAtQDRFK1Ha6jqKKTs46Zm5fw3LqgU1mRAT6CKTywVfFMHZCLm1hcQNScSMwVvjQ
|
Signature: 4AESGZzqBVfj5xQnMiPWAwzJnAtQDRFK1Ha6jqKKTs46Zm5fw3LqgU1mRAT6CKTywVfFMHZCLm1hcQNScSMwVvjQ
|
||||||
```
|
```
|
||||||
|
@ -505,7 +549,8 @@ In order to calculate the proper value of these stake pool tokens, we must updat
|
||||||
the total value managed by the stake pool every epoch.
|
the total value managed by the stake pool every epoch.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool update 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
$ spl-stake-pool update EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||||
|
Updating stake pool...
|
||||||
Signature: 3Yx1RH3Afqj5ckX8YvPCRt1DudVP4HuRPkh1dBPvTM9GqGxcB9ZXHGZPADVSZiaqKi166fevMG232EWxrRWswPtt
|
Signature: 3Yx1RH3Afqj5ckX8YvPCRt1DudVP4HuRPkh1dBPvTM9GqGxcB9ZXHGZPADVSZiaqKi166fevMG232EWxrRWswPtt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -513,13 +558,33 @@ If another user already updated the stake pool balance for the current epoch, we
|
||||||
see a different output.
|
see a different output.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool update 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC
|
$ spl-stake-pool update EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1
|
||||||
Stake pool balances are up to date, no update required.
|
Update not required
|
||||||
```
|
```
|
||||||
|
|
||||||
If no one updates the stake pool in the current epoch, the deposit and withdraw
|
If no one updates the stake pool in the current epoch, the deposit and withdraw
|
||||||
instructions will fail. The update instruction is permissionless, so any user
|
instructions will fail. The update instruction is permissionless, so any user
|
||||||
can run it before depositing or withdrawing.
|
can run it before depositing or withdrawing. As a convenience, the CLI attempts
|
||||||
|
to update before running any instruction on the stake pool.
|
||||||
|
|
||||||
|
If the stake pool transient stakes are in an unexpected state, and merges are
|
||||||
|
not possible, there is the option to only update the stake pool balances without
|
||||||
|
performing merges using the `--no-merge` flag.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ spl-stake-pool update EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 --no-merge
|
||||||
|
Updating stake pool...
|
||||||
|
Signature: 3Yx1RH3Afqj5ckX8YvPCRt1DudVP4HuRPkh1dBPvTM9GqGxcB9ZXHGZPADVSZiaqKi166fevMG232EWxrRWswPtt
|
||||||
|
```
|
||||||
|
|
||||||
|
Later on, whenever the transient stakes are ready to be merged, it is possible to
|
||||||
|
force another update in the same epoch using the `--force` flag.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ spl-stake-pool update EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 --force
|
||||||
|
Updating stake pool...
|
||||||
|
Signature: 3Yx1RH3Afqj5ckX8YvPCRt1DudVP4HuRPkh1dBPvTM9GqGxcB9ZXHGZPADVSZiaqKi166fevMG232EWxrRWswPtt
|
||||||
|
```
|
||||||
|
|
||||||
#### Withdraw stake
|
#### Withdraw stake
|
||||||
|
|
||||||
|
@ -529,7 +594,7 @@ staking derivative SPL tokens in exchange for an activated stake account.
|
||||||
Let's withdraw 0.02 staking derivative tokens from the stake pool.
|
Let's withdraw 0.02 staking derivative tokens from the stake pool.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool withdraw 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --amount 0.02 --withdraw-from 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
$ spl-stake-pool withdraw EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 0.02
|
||||||
Withdrawing from account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN, amount 8.867176377 SOL, 0.02 pool tokens
|
Withdrawing from account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN, amount 8.867176377 SOL, 0.02 pool tokens
|
||||||
Creating account to receive stake CZF2z3JJoDmJRcVjtsrz1BKUUGNL3VPW5FPFqge1bzmQ
|
Creating account to receive stake CZF2z3JJoDmJRcVjtsrz1BKUUGNL3VPW5FPFqge1bzmQ
|
||||||
Signature: 2xBPVPJ749AE4hHNCNYdjuHv1EdMvxm9uvvraWfTA7Urrvecwh9w64URCyLLroLQ2RKDGE2QELM2ZHd8qRkjavJM
|
Signature: 2xBPVPJ749AE4hHNCNYdjuHv1EdMvxm9uvvraWfTA7Urrvecwh9w64URCyLLroLQ2RKDGE2QELM2ZHd8qRkjavJM
|
||||||
|
@ -550,15 +615,58 @@ Stake Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||||
Withdraw Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
Withdraw Authority: 4SnSuUtJGKvk2GYpBwmEsWG53zTurVM8yXGsoiZQyMJn
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, the user can specify an existing stake account to receive their
|
Alternatively, the user can specify an existing uninitialized stake account to
|
||||||
stake using the `stake-receiver` parameter.
|
receive their stake using the `--stake-receiver` parameter.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ spl-stake-pool withdraw 3CLwo9CntMi4D1enHEFBe3pRJQzGJBCAYe66xFuEbmhC --amount 0.02 --withdraw-from 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF --stake-receiver CZF2z3JJoDmJRcVjtsrz1BKUUGNL3VPW5FPFqge1bzmQ
|
$ spl-stake-pool withdraw EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 --amount 0.02 --withdraw-from 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF --stake-receiver CZF2z3JJoDmJRcVjtsrz1BKUUGNL3VPW5FPFqge1bzmQ
|
||||||
Withdrawing from account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN, amount 8.867176377 SOL, 0.02 pool tokens
|
Withdrawing from account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN, amount 8.867176377 SOL, 0.02 pool tokens
|
||||||
Signature: 2xBPVPJ749AE4hHNCNYdjuHv1EdMvxm9uvvraWfTA7Urrvecwh9w64URCyLLroLQ2RKDGE2QELM2ZHd8qRkjavJM
|
Signature: 2xBPVPJ749AE4hHNCNYdjuHv1EdMvxm9uvvraWfTA7Urrvecwh9w64URCyLLroLQ2RKDGE2QELM2ZHd8qRkjavJM
|
||||||
```
|
```
|
||||||
|
|
||||||
|
By default, the withdraw command uses the fee payer's associated token account to
|
||||||
|
source the derivative tokens. It's possible to specify the SPL token account using
|
||||||
|
the `--pool-account` flag.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ spl-stake-pool withdraw EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 0.02 --pool-account 34XMHa3JUPv46ftU4dGHvemZ9oKVjnciRePYMcX3rjEF
|
||||||
|
Withdrawing from account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN, amount 8.867176377 SOL, 0.02 pool tokens
|
||||||
|
Creating account to receive stake CZF2z3JJoDmJRcVjtsrz1BKUUGNL3VPW5FPFqge1bzmQ
|
||||||
|
Signature: 2xBPVPJ749AE4hHNCNYdjuHv1EdMvxm9uvvraWfTA7Urrvecwh9w64URCyLLroLQ2RKDGE2QELM2ZHd8qRkjavJM
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the withdraw command will withdraw from the largest validator stake
|
||||||
|
accounts in the pool. It's also possible to specify a specific vote account for
|
||||||
|
the withdraw using the `--vote-account` flag.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ spl-stake-pool withdraw EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 0.02 --vote-account 2HUKQz7W2nXZSwrdX5RkfS2rLU4j1QZLjdGCHcoUKFh3
|
||||||
|
Withdrawing from account FYQB64aEzSmECvnG8RVvdAXBxRnzrLvcA3R22aGH2hUN, amount 8.867176377 SOL, 0.02 pool tokens
|
||||||
|
Creating account to receive stake CZF2z3JJoDmJRcVjtsrz1BKUUGNL3VPW5FPFqge1bzmQ
|
||||||
|
Signature: 2xBPVPJ749AE4hHNCNYdjuHv1EdMvxm9uvvraWfTA7Urrvecwh9w64URCyLLroLQ2RKDGE2QELM2ZHd8qRkjavJM
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the associated validator stake account must have enough lamports to
|
||||||
|
satisfy the pool token amount requested.
|
||||||
|
|
||||||
|
##### Special case: exiting pool with a delinquent staker
|
||||||
|
|
||||||
|
With the reserve stake, it's possible for a delinquent or malicious staker to
|
||||||
|
move all stake into the reserve through `decrease-validator-stake`, so the
|
||||||
|
staking derivatives will not gain rewards, and the stake pool users will not
|
||||||
|
be able to withdraw their funds.
|
||||||
|
|
||||||
|
To get around this case, it is also possible to withdraw from the stake pool's
|
||||||
|
reserve, but only if all of the validator stake accounts are at the minimum amount of
|
||||||
|
`1 SOL + stake account rent exemption`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ spl-stake-pool withdraw EjspffVUi2Tivszzs2JVj4GiSiMNYKyqZpgP3NeefBU1 0.02 --use-reserve
|
||||||
|
Withdrawing from account 33Hg3bvYrAwfqCzTMjAWZNAWC6H96qJNEdzGamfFjG4J, amount 8.867176377 SOL, 0.02 pool tokens
|
||||||
|
Creating account to receive stake 9E5YzXXu9NDhtMxWJKCwe2M8Sdz6vL6bcBS92U76PVtE
|
||||||
|
Signature: 4aZaeT9Azcq23PdKcjbQLseNveZVAQ4xMabBGQspfX316cE62Q2hoES373ExbT9y2JUhug7SgdybNaCjuZ6uqNYf
|
||||||
|
```
|
||||||
|
|
||||||
## Appendix
|
## Appendix
|
||||||
|
|
||||||
### Activated stakes
|
### Activated stakes
|
||||||
|
@ -569,6 +677,22 @@ are not equivalent to inactive, activating, or deactivating stakes due to the
|
||||||
time cost of staking. Otherwise, malicious actors can deposit stake in one state
|
time cost of staking. Otherwise, malicious actors can deposit stake in one state
|
||||||
and withdraw it in another state without waiting.
|
and withdraw it in another state without waiting.
|
||||||
|
|
||||||
|
### Transient stake accounts
|
||||||
|
|
||||||
|
Each validator gets one transient stake account, so the staker can only
|
||||||
|
perform one action at a time on a validator. It's impossible to increase
|
||||||
|
and decrease the stake on a validator at the same time. The staker must wait for
|
||||||
|
the existing transient stake account to get merged during an `update` instruction
|
||||||
|
before performing a new action.
|
||||||
|
|
||||||
|
### Reserve stake account
|
||||||
|
|
||||||
|
Every stake pool is initialized with an undelegated reserve stake account, used
|
||||||
|
to hold undelegated stake in process of rebalancing. After the staker decreases
|
||||||
|
the stake on a validator, one epoch later, the update operation will merge the
|
||||||
|
decreased stake into the reserve. Conversely, whenever the staker increases the
|
||||||
|
stake on a validator, the lamports are drawn from the reserve stake account.
|
||||||
|
|
||||||
### Staking Credits Observed on Deposit
|
### Staking Credits Observed on Deposit
|
||||||
|
|
||||||
A deposited stake account's "credits observed" must match the destination
|
A deposited stake account's "credits observed" must match the destination
|
||||||
|
|
|
@ -83,6 +83,15 @@ Hardware Wallet URL (See [URL spec](https://docs.solana.com/wallet-guide/hardwar
|
||||||
solana config set --keypair usb://ledger/
|
solana config set --keypair usb://ledger/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Airdrop SOL
|
||||||
|
|
||||||
|
Creating tokens and accounts requires SOL for account rent deposits and
|
||||||
|
transaction fees. If the cluster you are targeting offers a faucet, you can get
|
||||||
|
a little SOL for testing:
|
||||||
|
```
|
||||||
|
solana airdrop 1
|
||||||
|
```
|
||||||
|
|
||||||
### Example: Creating your own fungible token
|
### Example: Creating your own fungible token
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
@ -109,7 +118,7 @@ Signature: 42Sa5eK9dMEQyvD9GMHuKxXf55WLZ7tfjabUKDhNoZRAxj9MsnN7omriWMEHXLea3aYpj
|
||||||
|
|
||||||
`7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi` is now an empty account:
|
`7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi` is now an empty account:
|
||||||
```sh
|
```sh
|
||||||
$ spl-token balance 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
$ spl-token balance AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
||||||
0
|
0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -126,7 +135,7 @@ The token `supply` and account `balance` now reflect the result of minting:
|
||||||
```sh
|
```sh
|
||||||
$ spl-token supply AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
$ spl-token supply AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
||||||
100
|
100
|
||||||
$ spl-token balance 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
$ spl-token balance AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
||||||
100
|
100
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -166,7 +175,7 @@ address by running `solana address` and provides it to the sender.
|
||||||
|
|
||||||
The sender then runs:
|
The sender then runs:
|
||||||
```
|
```
|
||||||
$ spl-token transfer 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
$ spl-token transfer AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
||||||
Transfer 50 tokens
|
Transfer 50 tokens
|
||||||
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
||||||
Recipient: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
Recipient: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
||||||
|
@ -184,7 +193,7 @@ The receiver obtains their wallet address by running `solana address` and provid
|
||||||
The sender then runs to fund the receiver's associated token account, at the
|
The sender then runs to fund the receiver's associated token account, at the
|
||||||
sender's expense, and then transfers 50 tokens into it:
|
sender's expense, and then transfers 50 tokens into it:
|
||||||
```
|
```
|
||||||
$ spl-token transfer --fund-recipient 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
$ spl-token transfer --fund-recipient AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
||||||
Transfer 50 tokens
|
Transfer 50 tokens
|
||||||
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
||||||
Recipient: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
Recipient: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
||||||
|
@ -228,9 +237,9 @@ CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe
|
||||||
|
|
||||||
### Example: Create a non-fungible token
|
### Example: Create a non-fungible token
|
||||||
|
|
||||||
Create the token type,
|
Create the token type with nine decimal places,
|
||||||
```
|
```
|
||||||
$ spl-token create-token
|
$ spl-token create-token --decimals 9
|
||||||
Creating token 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
|
Creating token 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
|
||||||
Signature: 4kz82JUey1B9ki1McPW7NYv1NqPKCod6WNptSkYqtuiEsQb9exHaktSAHJJsm4YxuGNW4NugPJMFX9ee6WA2dXts
|
Signature: 4kz82JUey1B9ki1McPW7NYv1NqPKCod6WNptSkYqtuiEsQb9exHaktSAHJJsm4YxuGNW4NugPJMFX9ee6WA2dXts
|
||||||
```
|
```
|
||||||
|
@ -264,7 +273,7 @@ Now the `7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM` account holds the
|
||||||
one and only `559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z` token:
|
one and only `559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z` token:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ spl-token account-info 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
|
$ spl-token account-info 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
|
||||||
|
|
||||||
Address: 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
|
Address: 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
|
||||||
Balance: 1
|
Balance: 1
|
||||||
|
@ -520,7 +529,7 @@ There is a rich set of JSON RPC methods available for use with SPL Token:
|
||||||
|
|
||||||
See https://docs.solana.com/apps/jsonrpc-api for more details.
|
See https://docs.solana.com/apps/jsonrpc-api for more details.
|
||||||
|
|
||||||
Additionally the versatile `getProgramAcccounts` JSON RPC method can be employed in various ways to fetch SPL Token accounts of interest.
|
Additionally the versatile `getProgramAccounts` JSON RPC method can be employed in various ways to fetch SPL Token accounts of interest.
|
||||||
|
|
||||||
### Finding all token accounts for a specific mint
|
### Finding all token accounts for a specific mint
|
||||||
|
|
||||||
|
@ -553,7 +562,7 @@ curl http://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/js
|
||||||
```
|
```
|
||||||
|
|
||||||
The `"dataSize": 165` filter selects all [Token
|
The `"dataSize": 165` filter selects all [Token
|
||||||
Acccount](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L86-L106)s,
|
Account](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L86-L106)s,
|
||||||
and then the `"memcmp": ...` filter selects based on the
|
and then the `"memcmp": ...` filter selects based on the
|
||||||
[mint](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L88)
|
[mint](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L88)
|
||||||
address within each token account.
|
address within each token account.
|
||||||
|
@ -588,7 +597,7 @@ curl http://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/js
|
||||||
```
|
```
|
||||||
|
|
||||||
The `"dataSize": 165` filter selects all [Token
|
The `"dataSize": 165` filter selects all [Token
|
||||||
Acccount](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L86-L106)s,
|
Account](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L86-L106)s,
|
||||||
and then the `"memcmp": ...` filter selects based on the
|
and then the `"memcmp": ...` filter selects based on the
|
||||||
[owner](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L90)
|
[owner](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L90)
|
||||||
address within each token account.
|
address within each token account.
|
||||||
|
@ -852,3 +861,13 @@ the maximum allowed transaction size, remove those extra clean up instructions.
|
||||||
They can be cleaned up during the next send operation.
|
They can be cleaned up during the next send operation.
|
||||||
|
|
||||||
The `spl-token gc` command provides an example implementation of this cleanup process.
|
The `spl-token gc` command provides an example implementation of this cleanup process.
|
||||||
|
|
||||||
|
|
||||||
|
### Token Vesting Contract:
|
||||||
|
This program allows you to lock arbitrary SPL tokens and release the locked tokens with a determined unlock schedule. An `unlock schedule` is made of a `unix timestamp` and a token `amount`, when initializing a vesting contract, the creator can pass an array of `unlock schedule` with an arbitrary size giving the creator of the contract complete control of how the tokens unlock over time.
|
||||||
|
|
||||||
|
Unlocking works by pushing a permissionless crank on the contract that moves the tokens to the pre-specified address. The recipient address of a vesting contract can be modified by the owner of the current recipient key, meaning that vesting contract locked tokens can be traded.
|
||||||
|
|
||||||
|
- Code: [https://github.com/Bonfida/token-vesting](https://github.com/Bonfida/token-vesting)
|
||||||
|
- UI: [https://vesting.bonfida.com/#/](https://vesting.bonfida.com/#/)
|
||||||
|
- Audit: The audit was conducted by Kudelski, the report can be found [here](https://github.com/Bonfida/token-vesting/blob/master/audit/Bonfida_SecurityAssessment_Vesting_Final050521.pdf)
|
|
@ -22,7 +22,7 @@ extern uint64_t do_invoke(SolParameters *params) {
|
||||||
const SolSignerSeeds signers_seeds[] = {{seeds, SOL_ARRAY_SIZE(seeds)}};
|
const SolSignerSeeds signers_seeds[] = {{seeds, SOL_ARRAY_SIZE(seeds)}};
|
||||||
|
|
||||||
SolPubkey expected_allocated_key;
|
SolPubkey expected_allocated_key;
|
||||||
if (SUCCESS == sol_create_program_address(seeds, SOL_ARRAY_SIZE(seeds),
|
if (SUCCESS != sol_create_program_address(seeds, SOL_ARRAY_SIZE(seeds),
|
||||||
params->program_id,
|
params->program_id,
|
||||||
&expected_allocated_key)) {
|
&expected_allocated_key)) {
|
||||||
return ERROR_INVALID_INSTRUCTION_DATA;
|
return ERROR_INVALID_INSTRUCTION_DATA;
|
||||||
|
@ -31,8 +31,7 @@ extern uint64_t do_invoke(SolParameters *params) {
|
||||||
return ERROR_INVALID_ARGUMENT;
|
return ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
SolAccountMeta arguments[] = {{system_program_info->key, false, false},
|
SolAccountMeta arguments[] = {{allocated_info->key, true, true}};
|
||||||
{allocated_info->key, true, true}};
|
|
||||||
uint8_t data[4 + 8]; // Enough room for the Allocate instruction
|
uint8_t data[4 + 8]; // Enough room for the Allocate instruction
|
||||||
*(uint16_t *)data = 8; // Allocate instruction enum value
|
*(uint16_t *)data = 8; // Allocate instruction enum value
|
||||||
*(uint64_t *)(data + 4) = SIZE; // Size to allocate
|
*(uint64_t *)(data + 4) = SIZE; // Size to allocate
|
||||||
|
|
|
@ -13,11 +13,11 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.6.2"
|
solana-program-test = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -15,11 +15,11 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.6.2"
|
solana-program-test = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -13,11 +13,11 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.6.2"
|
solana-program-test = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -13,11 +13,11 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.6.2"
|
solana-program-test = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -12,11 +12,11 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.6.2"
|
solana-program-test = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -10,11 +10,11 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
clap = "2.33.3"
|
clap = "2.33.3"
|
||||||
solana-clap-utils = "1.6.2"
|
solana-clap-utils = "1.6.7"
|
||||||
solana-cli-config = "1.6.2"
|
solana-cli-config = "1.6.7"
|
||||||
solana-client = "1.6.2"
|
solana-client = "1.6.7"
|
||||||
solana-logger = "1.6.2"
|
solana-logger = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
spl-feature-proposal = { version = "1.0", path = "../program", features = ["no-entrypoint"] }
|
spl-feature-proposal = { version = "1.0", path = "../program", features = ["no-entrypoint"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -12,15 +12,14 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
borsh = "0.7.1"
|
borsh = "0.8"
|
||||||
borsh-derive = "0.8.1"
|
borsh-derive = "0.8.1"
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
spl-token = { version = "3.1", path = "../../token/program", features = ["no-entrypoint"] }
|
spl-token = { version = "3.1", path = "../../token/program", features = ["no-entrypoint"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
futures = "0.3"
|
solana-program-test = "1.6.7"
|
||||||
solana-program-test = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -155,13 +155,12 @@ pub fn tally(feature_proposal_address: &Pubkey) -> Instruction {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::borsh_utils;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_packed_len() {
|
fn test_get_packed_len() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FeatureProposalInstruction::get_packed_len(),
|
FeatureProposalInstruction::get_packed_len(),
|
||||||
borsh_utils::get_packed_len::<FeatureProposalInstruction>()
|
solana_program::borsh::get_packed_len::<FeatureProposalInstruction>()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
pub mod borsh_utils;
|
|
||||||
mod entrypoint;
|
mod entrypoint;
|
||||||
pub mod instruction;
|
pub mod instruction;
|
||||||
pub mod processor;
|
pub mod processor;
|
||||||
|
|
|
@ -59,13 +59,12 @@ impl Pack for FeatureProposal {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::borsh_utils;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_packed_len() {
|
fn test_get_packed_len() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FeatureProposal::get_packed_len(),
|
FeatureProposal::get_packed_len(),
|
||||||
borsh_utils::get_packed_len::<FeatureProposal>()
|
solana_program::borsh::get_packed_len::<FeatureProposal>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
// Mark this test as BPF-only due to current `ProgramTest` limitations when CPIing into the system program
|
// Mark this test as BPF-only due to current `ProgramTest` limitations when CPIing into the system program
|
||||||
#![cfg(feature = "test-bpf")]
|
#![cfg(feature = "test-bpf")]
|
||||||
|
|
||||||
use futures::{Future, FutureExt};
|
use {
|
||||||
use solana_program::{
|
solana_program::{
|
||||||
feature::{self, Feature},
|
feature::{self, Feature},
|
||||||
program_option::COption,
|
program_option::COption,
|
||||||
program_pack::Pack,
|
pubkey::Pubkey,
|
||||||
pubkey::Pubkey,
|
system_program,
|
||||||
system_program,
|
},
|
||||||
|
solana_program_test::*,
|
||||||
|
solana_sdk::{
|
||||||
|
signature::{Keypair, Signer},
|
||||||
|
transaction::Transaction,
|
||||||
|
},
|
||||||
|
spl_feature_proposal::{instruction::*, state::*, *},
|
||||||
};
|
};
|
||||||
use solana_program_test::*;
|
|
||||||
use solana_sdk::{
|
|
||||||
signature::{Keypair, Signer},
|
|
||||||
transaction::Transaction,
|
|
||||||
};
|
|
||||||
use spl_feature_proposal::{instruction::*, state::*, *};
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
fn program_test() -> ProgramTest {
|
fn program_test() -> ProgramTest {
|
||||||
ProgramTest::new(
|
ProgramTest::new(
|
||||||
|
@ -25,21 +24,6 @@ fn program_test() -> ProgramTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch and unpack account data
|
|
||||||
fn get_account_data<T: Pack>(
|
|
||||||
banks_client: &mut BanksClient,
|
|
||||||
address: Pubkey,
|
|
||||||
) -> impl Future<Output = std::io::Result<T>> + '_ {
|
|
||||||
banks_client.get_account(address).map(|result| {
|
|
||||||
let account =
|
|
||||||
result?.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "account not found"))?;
|
|
||||||
|
|
||||||
T::unpack_from_slice(&account.data)
|
|
||||||
.ok()
|
|
||||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Failed to deserialize account"))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_basic() {
|
async fn test_basic() {
|
||||||
let feature_proposal = Keypair::new();
|
let feature_proposal = Keypair::new();
|
||||||
|
@ -68,16 +52,17 @@ async fn test_basic() {
|
||||||
banks_client.process_transaction(transaction).await.unwrap();
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
// Confirm feature id account is now funded and allocated, but not assigned
|
// Confirm feature id account is now funded and allocated, but not assigned
|
||||||
let feature_id_acccount = banks_client
|
let feature_id_account = banks_client
|
||||||
.get_account(feature_id_address)
|
.get_account(feature_id_address)
|
||||||
.await
|
.await
|
||||||
.expect("success")
|
.expect("success")
|
||||||
.expect("some account");
|
.expect("some account");
|
||||||
assert_eq!(feature_id_acccount.owner, system_program::id());
|
assert_eq!(feature_id_account.owner, system_program::id());
|
||||||
assert_eq!(feature_id_acccount.data.len(), Feature::size_of());
|
assert_eq!(feature_id_account.data.len(), Feature::size_of());
|
||||||
|
|
||||||
// Confirm mint account state
|
// Confirm mint account state
|
||||||
let mint = get_account_data::<spl_token::state::Mint>(&mut banks_client, mint_address)
|
let mint = banks_client
|
||||||
|
.get_packed_account_data::<spl_token::state::Mint>(mint_address)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(mint.supply, 42);
|
assert_eq!(mint.supply, 42);
|
||||||
|
@ -86,20 +71,20 @@ async fn test_basic() {
|
||||||
assert_eq!(mint.mint_authority, COption::Some(mint_address));
|
assert_eq!(mint.mint_authority, COption::Some(mint_address));
|
||||||
|
|
||||||
// Confirm distributor token account state
|
// Confirm distributor token account state
|
||||||
let distributor_token =
|
let distributor_token = banks_client
|
||||||
get_account_data::<spl_token::state::Account>(&mut banks_client, distributor_token_address)
|
.get_packed_account_data::<spl_token::state::Account>(distributor_token_address)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(distributor_token.amount, 42);
|
assert_eq!(distributor_token.amount, 42);
|
||||||
assert_eq!(distributor_token.mint, mint_address);
|
assert_eq!(distributor_token.mint, mint_address);
|
||||||
assert_eq!(distributor_token.owner, feature_proposal.pubkey());
|
assert_eq!(distributor_token.owner, feature_proposal.pubkey());
|
||||||
assert!(distributor_token.close_authority.is_none());
|
assert!(distributor_token.close_authority.is_none());
|
||||||
|
|
||||||
// Confirm acceptance token account state
|
// Confirm acceptance token account state
|
||||||
let acceptance_token =
|
let acceptance_token = banks_client
|
||||||
get_account_data::<spl_token::state::Account>(&mut banks_client, acceptance_token_address)
|
.get_packed_account_data::<spl_token::state::Account>(acceptance_token_address)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(acceptance_token.amount, 0);
|
assert_eq!(acceptance_token.amount, 0);
|
||||||
assert_eq!(acceptance_token.mint, mint_address);
|
assert_eq!(acceptance_token.mint, mint_address);
|
||||||
assert_eq!(acceptance_token.owner, id());
|
assert_eq!(acceptance_token.owner, id());
|
||||||
|
@ -115,15 +100,17 @@ async fn test_basic() {
|
||||||
banks_client.process_transaction(transaction).await.unwrap();
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
// Confirm feature id account is not yet assigned
|
// Confirm feature id account is not yet assigned
|
||||||
let feature_id_acccount = banks_client
|
let feature_id_account = banks_client
|
||||||
.get_account(feature_id_address)
|
.get_account(feature_id_address)
|
||||||
.await
|
.await
|
||||||
.expect("success")
|
.expect("success")
|
||||||
.expect("some account");
|
.expect("some account");
|
||||||
assert_eq!(feature_id_acccount.owner, system_program::id());
|
assert_eq!(feature_id_account.owner, system_program::id());
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
get_account_data::<FeatureProposal>(&mut banks_client, feature_proposal.pubkey()).await,
|
banks_client
|
||||||
|
.get_packed_account_data::<FeatureProposal>(feature_proposal.pubkey())
|
||||||
|
.await,
|
||||||
Ok(FeatureProposal::Pending(_))
|
Ok(FeatureProposal::Pending(_))
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -158,16 +145,18 @@ async fn test_basic() {
|
||||||
banks_client.process_transaction(transaction).await.unwrap();
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
// Confirm feature id account is now assigned
|
// Confirm feature id account is now assigned
|
||||||
let feature_id_acccount = banks_client
|
let feature_id_account = banks_client
|
||||||
.get_account(feature_id_address)
|
.get_account(feature_id_address)
|
||||||
.await
|
.await
|
||||||
.expect("success")
|
.expect("success")
|
||||||
.expect("some account");
|
.expect("some account");
|
||||||
assert_eq!(feature_id_acccount.owner, feature::id());
|
assert_eq!(feature_id_account.owner, feature::id());
|
||||||
|
|
||||||
// Confirm feature proposal account state
|
// Confirm feature proposal account state
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
get_account_data::<FeatureProposal>(&mut banks_client, feature_proposal.pubkey()).await,
|
banks_client
|
||||||
|
.get_packed_account_data::<FeatureProposal>(feature_proposal.pubkey())
|
||||||
|
.await,
|
||||||
Ok(FeatureProposal::Accepted {
|
Ok(FeatureProposal::Accepted {
|
||||||
tokens_upon_acceptance: 42
|
tokens_upon_acceptance: 42
|
||||||
})
|
})
|
||||||
|
@ -197,7 +186,9 @@ async fn test_expired() {
|
||||||
banks_client.process_transaction(transaction).await.unwrap();
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
get_account_data::<FeatureProposal>(&mut banks_client, feature_proposal.pubkey()).await,
|
banks_client
|
||||||
|
.get_packed_account_data::<FeatureProposal>(feature_proposal.pubkey())
|
||||||
|
.await,
|
||||||
Ok(FeatureProposal::Pending(_))
|
Ok(FeatureProposal::Pending(_))
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -208,7 +199,9 @@ async fn test_expired() {
|
||||||
banks_client.process_transaction(transaction).await.unwrap();
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
get_account_data::<FeatureProposal>(&mut banks_client, feature_proposal.pubkey()).await,
|
banks_client
|
||||||
|
.get_packed_account_data::<FeatureProposal>(feature_proposal.pubkey())
|
||||||
|
.await,
|
||||||
Ok(FeatureProposal::Expired)
|
Ok(FeatureProposal::Expired)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
# Governance
|
||||||
|
|
||||||
|
Governance is a program the chief purpose of which is to control the upgrade of other programs through democratic means.
|
||||||
|
It can also be used as an authority provider for mints and other forms of access control as well where we may want
|
||||||
|
a voting population to vote on disbursement of access or funds collectively.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Accounts diagram
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Governance Realm account
|
||||||
|
|
||||||
|
Governance Realm ties Community Token Mint and optional Council Token mint to create a realm
|
||||||
|
for any governance pertaining to the community of the token holders.
|
||||||
|
For example a trading protocol can issue a governance token and use it to create its governance realm.
|
||||||
|
|
||||||
|
Once a realm is created voters can deposit Governing tokens (Community or Council) to the realm and
|
||||||
|
use the deposited amount as their voting weight to vote on Proposals within that realm.
|
||||||
|
|
||||||
|
### Program Governance account
|
||||||
|
|
||||||
|
The basic building block of governance to update programs is the ProgramGovernance account.
|
||||||
|
It ties a governed Program ID and holds configuration options defining governance rules.
|
||||||
|
The governed Program ID is used as the seed for a [Program Derived Address](https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses),
|
||||||
|
and this program derived address is what is used as the address of the Governance account for your Program ID
|
||||||
|
and the corresponding Governance mint and Council mint (if provided).
|
||||||
|
|
||||||
|
What this means is that there can only ever be ONE Governance account for a given Program.
|
||||||
|
The governance program validates at creation time of the Governance account that the current upgrade authority of the program
|
||||||
|
taken under governance signed the transaction.
|
||||||
|
|
||||||
|
Note: In future versions, once allowed in solana runtime, the governance program will take over the upgrade authority
|
||||||
|
of the governed program when the Governance account is created.
|
||||||
|
|
||||||
|
### How does authority work?
|
||||||
|
|
||||||
|
Governance can handle arbitrary executions of code, but it's real power lies in the power to upgrade programs.
|
||||||
|
It does this through executing commands to the bpf-upgradable-loader program.
|
||||||
|
Bpf-upgradable-loader allows any signer who has Upgrade authority over a Buffer account and the Program account itself
|
||||||
|
to upgrade it using its Upgrade command.
|
||||||
|
Normally, this is the developer who created and deployed the program, and this creation of the Buffer account containing
|
||||||
|
the new program data and overwriting of the existing Program account's data with it is handled in the background for you
|
||||||
|
by the Solana program deploy cli command.
|
||||||
|
However, in order for Governance to be useful, Governance now needs this authority.
|
||||||
|
|
||||||
|
### Proposal accounts
|
||||||
|
|
||||||
|
A Proposal is an instance of a Governance created to vote on and execute given set of changes.
|
||||||
|
It is created by someone (Proposal Admin) and tied to a given Governance account
|
||||||
|
and has a set of executable commands to it, a name and a description.
|
||||||
|
It goes through various states (draft, voting, executing) and users can vote on it
|
||||||
|
if they have relevant Community or Council tokens.
|
||||||
|
It's rules are determined by the Governance account that it is tied to, and when it executes,
|
||||||
|
it is only eligible to use the [Program Derived Address](https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses)
|
||||||
|
authority given by the Governance account.
|
||||||
|
So a Proposal for Sushi cannot for instance upgrade the Program for Uniswap.
|
||||||
|
|
||||||
|
When a Proposal is created by a user then the user becomes Proposal Admin and receives an Admin an Signatory token.
|
||||||
|
With this power the Admin can add other Signatories to the Proposal.
|
||||||
|
These Signatories can then add commands to the Proposal and/or sign off on the Proposal.
|
||||||
|
Once all Signatories have signed off on the Proposal the Proposal leaves Draft state and enters Voting state.
|
||||||
|
Voting state lasts as long as the Governance has it configured to last, and during this time
|
||||||
|
people holding Community (or Council) tokens may vote on the Proposal.
|
||||||
|
Once the Proposal is "tipped" it either enters the Defeated or Executing state.
|
||||||
|
If Executed, it enters Completed state once all commands have been run.
|
||||||
|
|
||||||
|
A command can be run by any one at any time after the `instruction_hold_up_time` length has transpired on the given command.
|
||||||
|
|
||||||
|
### SingleSignerInstruction
|
||||||
|
|
||||||
|
We only support one kind of executable command right now, and this is the `SingleSignerInstruction` type.
|
||||||
|
A Proposal can have a certain number of these, and they run independently of each other.
|
||||||
|
These contain the actual data for a command, and how long after the voting phase a user must wait before they can be executed.
|
||||||
|
|
||||||
|
### Voting Dynamics
|
||||||
|
|
||||||
|
When a Proposal is created and signed by its Signatories voters can start voting on it using their voting weight,
|
||||||
|
equal to deposited governing tokens into the realm. A vote is tipped once it passes the defined `vote_threshold` of votes
|
||||||
|
and enters Succeeded or Defeated state. If Succeeded then Proposal instructions can be executed after they hold_up_time passes.
|
||||||
|
|
||||||
|
Users can relinquish their vote any time during Proposal lifetime, but once Proposal it tipped their vote can't be changed.
|
||||||
|
|
||||||
|
### Community and Councils governing tokens
|
||||||
|
|
||||||
|
Each Governance Realm that gets created has the option to also have a Council mint.
|
||||||
|
A council mint is simply a separate mint from the Community mint.
|
||||||
|
What this means is that users can submit Proposals that have a different voting population from a different mint
|
||||||
|
that can affect the same program. A practical application of this policy may be to have a very large population control
|
||||||
|
major version bumps of Solana via normal SOL, for instance, but hot fixes be controlled via Council tokens,
|
||||||
|
of which there may be only 30, and which may be themselves minted and distributed via proposals by the governing population.
|
||||||
|
|
||||||
|
### Proposal Workflow
|
||||||
|
|
||||||
|

|
|
@ -0,0 +1,29 @@
|
||||||
|
[package]
|
||||||
|
name = "spl-governance"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Solana Program Library Governance"
|
||||||
|
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||||
|
repository = "https://github.com/solana-labs/solana-program-library"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
|
||||||
|
[features]
|
||||||
|
no-entrypoint = []
|
||||||
|
test-bpf = []
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
solana-program = "1.6.7"
|
||||||
|
thiserror = "1.0"
|
||||||
|
num-derive = "0.3"
|
||||||
|
num-traits = "0.2"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
assert_matches = "1.5.0"
|
||||||
|
solana-program-test = "1.6.7"
|
||||||
|
solana-sdk = "1.6.7"
|
||||||
|
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "lib"]
|
|
@ -0,0 +1,2 @@
|
||||||
|
[target.bpfel-unknown-unknown.dependencies.std]
|
||||||
|
features = []
|
|
@ -0,0 +1,22 @@
|
||||||
|
//! Program entrypoint definitions
|
||||||
|
#![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))]
|
||||||
|
|
||||||
|
use crate::{error::GovernanceError, processor};
|
||||||
|
use solana_program::{
|
||||||
|
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult,
|
||||||
|
program_error::PrintProgramError, pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
|
||||||
|
entrypoint!(process_instruction);
|
||||||
|
fn process_instruction(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
instruction_data: &[u8],
|
||||||
|
) -> ProgramResult {
|
||||||
|
if let Err(error) = processor::process_instruction(program_id, accounts, instruction_data) {
|
||||||
|
// catch the error so we can print it
|
||||||
|
error.print::<GovernanceError>();
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
//! Error types
|
||||||
|
|
||||||
|
use num_derive::FromPrimitive;
|
||||||
|
use solana_program::{
|
||||||
|
decode_error::DecodeError,
|
||||||
|
msg,
|
||||||
|
program_error::{PrintProgramError, ProgramError},
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Errors that may be returned by the Governance program.
|
||||||
|
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
|
||||||
|
pub enum GovernanceError {
|
||||||
|
/// Invalid instruction passed to program.
|
||||||
|
#[error("Invalid instruction passed to program")]
|
||||||
|
InvalidInstruction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrintProgramError for GovernanceError {
|
||||||
|
fn print<E>(&self) {
|
||||||
|
msg!(&self.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GovernanceError> for ProgramError {
|
||||||
|
fn from(e: GovernanceError) -> Self {
|
||||||
|
ProgramError::Custom(e as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DecodeError<T> for GovernanceError {
|
||||||
|
fn type_of() -> &'static str {
|
||||||
|
"Governance Error"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,245 @@
|
||||||
|
//! Program instructions
|
||||||
|
|
||||||
|
use solana_program::{instruction::Instruction, pubkey::Pubkey};
|
||||||
|
|
||||||
|
use crate::state::enums::GoverningTokenType;
|
||||||
|
|
||||||
|
/// Yes/No Vote
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Vote {
|
||||||
|
/// Yes vote
|
||||||
|
Yes,
|
||||||
|
/// No vote
|
||||||
|
No,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Instructions supported by the Governance program
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
pub enum GovernanceInstruction {
|
||||||
|
/// Creates Governance Realm account which aggregates governances for given Community Mint and optional Council Mint
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Governance Realm account. PDA seeds:['governance',name]
|
||||||
|
/// 1. `[]` Community Token Mint
|
||||||
|
/// 2. `[writable]` Community Token Holding account. PDA seeds: ['governance',realm,community_mint]
|
||||||
|
/// The account will be created with the Realm PDA as its owner
|
||||||
|
/// 3. `[signer]` Payer
|
||||||
|
/// 4. `[]` System
|
||||||
|
/// 5. `[]` SPL Token
|
||||||
|
/// 6. `[]` Sysvar Rent
|
||||||
|
/// 7. `[]` Council Token Mint - optional
|
||||||
|
/// 8. `[writable]` Council Token Holding account - optional. . PDA seeds: ['governance',realm,council_mint]
|
||||||
|
/// The account will be created with the Realm PDA as its owner
|
||||||
|
CreateRealm {
|
||||||
|
/// UTF-8 encoded Governance Realm name
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Deposits governing tokens (Community or Council) to Governance Realm and establishes your voter weight to be used for voting within the Realm
|
||||||
|
/// Note: If subsequent (top up) deposit is made and there are active votes for the Voter then the vote weights won't be updated automatically
|
||||||
|
/// It can be done by relinquishing votes on active Proposals and voting again with the new weight
|
||||||
|
///
|
||||||
|
/// 0. `[]` Governance Realm account
|
||||||
|
/// 1. `[writable]` Governing Token Holding account. PDA seeds: ['governance',realm, governing_token_mint]
|
||||||
|
/// 2. `[writable]` Governing Token Source account. All tokens from the account will be transferred to the Holding account
|
||||||
|
/// 3. `[signer]` Governing Token Owner account
|
||||||
|
/// 4. `[writable]` Voter Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||||
|
/// 5. `[signer]` Payer
|
||||||
|
/// 6. `[]` System
|
||||||
|
/// 7. `[]` SPL Token
|
||||||
|
DepositGoverningTokens {},
|
||||||
|
|
||||||
|
/// Withdraws governing tokens (Community or Council) from Governance Realm and downgrades your voter weight within the Realm
|
||||||
|
/// Note: It's only possible to withdraw tokens if the Voter doesn't have any outstanding active votes
|
||||||
|
/// If there are any outstanding votes then they must be relinquished before tokens could be withdrawn
|
||||||
|
///
|
||||||
|
/// 0. `[]` Governance Realm account
|
||||||
|
/// 1. `[writable]` Governing Token Holding account. PDA seeds: ['governance',realm, governing_token_mint]
|
||||||
|
/// 2. `[writable]` Governing Token Destination account. All tokens will be transferred to this account
|
||||||
|
/// 3. `[signer]` Governing Token Owner account
|
||||||
|
/// 4. `[writable]` Voter Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||||
|
/// 5. `[]` SPL Token
|
||||||
|
WithdrawGoverningTokens {},
|
||||||
|
|
||||||
|
/// Sets vote authority for the given Realm and Governing Token Mint (Community or Council)
|
||||||
|
/// The vote authority would have voting rights and could vote on behalf of the Governing Token Owner
|
||||||
|
///
|
||||||
|
/// 0. `[signer]` Governing Token Owner
|
||||||
|
/// 1. `[writable]` Voter Record
|
||||||
|
SetVoteAuthority {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
/// Governance Realm the new vote authority is set for
|
||||||
|
realm: Pubkey,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
/// Governing Token Mint the vote authority is granted over
|
||||||
|
governing_token_mint: Pubkey,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
/// New vote authority
|
||||||
|
vote_authority: Pubkey,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Creates Program Governance account which governs an upgradable program
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Governance account. PDA seeds: ['governance', governed_program]
|
||||||
|
/// 1. `[]` Account of the Program governed by this Governance account
|
||||||
|
/// 2. `[writable]` Program Data account of the Program governed by this Governance account
|
||||||
|
/// 3. `[signer]` Current Upgrade Authority account of the Program governed by this Governance account
|
||||||
|
/// 4. `[]` Governance Realm the Program Governance belongs to
|
||||||
|
/// 5. `[signer]` Payer
|
||||||
|
/// 6. `[]` System account
|
||||||
|
/// 7. `[]` Bpf_upgrade_loader account
|
||||||
|
CreateProgramGovernance {
|
||||||
|
/// Voting threshold in % required to tip the vote
|
||||||
|
/// It's the percentage of tokens out of the entire pool of governance tokens eligible to vote
|
||||||
|
vote_threshold: u8,
|
||||||
|
|
||||||
|
/// Minimum waiting time in slots for an instruction to be executed after proposal is voted on
|
||||||
|
min_instruction_hold_up_time: u64,
|
||||||
|
|
||||||
|
/// Time limit in slots for proposal to be open for voting
|
||||||
|
max_voting_time: u64,
|
||||||
|
|
||||||
|
/// Minimum % of tokens for a governance token owner to be able to create proposal
|
||||||
|
/// It's the percentage of tokens out of the entire pool of governance tokens eligible to vote
|
||||||
|
token_threshold_to_create_proposal: u8,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Create Proposal account for Instructions that will be executed at various slots in the future
|
||||||
|
/// The instruction also grants Admin and Signatory token to the provided account
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Uninitialized Proposal account
|
||||||
|
/// 1. `[writable]` Initialized Governance account
|
||||||
|
/// 2. `[writable]` Initialized Signatory Mint account
|
||||||
|
/// 3. `[writable]` Initialized Admin Mint account
|
||||||
|
/// 4. `[writable]` Initialized Admin account for the issued admin token
|
||||||
|
/// 5. `[writable]` Initialized Signatory account for the issued signatory token
|
||||||
|
/// 6. '[]` Token program account
|
||||||
|
/// 7. `[]` Rent sysvar
|
||||||
|
CreateProposal {
|
||||||
|
/// Link to gist explaining proposal
|
||||||
|
description_link: String,
|
||||||
|
|
||||||
|
/// UTF-8 encoded name of the proposal
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
/// The Governing token (Community or Council) which will be used for voting on the Proposal
|
||||||
|
governing_token_type: GoverningTokenType,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// [Requires Admin token]
|
||||||
|
/// Adds a signatory to the Proposal which means this Proposal can't leave Draft state until yet another Signatory signs
|
||||||
|
/// As a result of this call the new Signatory will receive a Signatory Token which then can be used to Sign proposal
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Proposal account
|
||||||
|
/// 1. `[writable]` Initialized Signatory account
|
||||||
|
/// 2. `[writable]` Initialized Signatory Mint account
|
||||||
|
/// 3. `[signer]` Admin account
|
||||||
|
/// 4. '[]` Token program account
|
||||||
|
AddSignatory,
|
||||||
|
|
||||||
|
/// [Requires Admin token]
|
||||||
|
/// Removes a Signatory from the Proposal
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Proposal account
|
||||||
|
/// 1. `[writable]` Signatory account to remove token from
|
||||||
|
/// 2. `[writable]` Signatory Mint account
|
||||||
|
/// 3. `[signer]` Admin account
|
||||||
|
/// 4. '[]` Token program account
|
||||||
|
RemoveSignatory,
|
||||||
|
|
||||||
|
/// [Requires Admin token]
|
||||||
|
/// Adds an instruction to the Proposal. Max of 5 of any type. More than 5 will throw error
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Proposal account
|
||||||
|
/// 1. `[writable]` Uninitialized Proposal SingleSignerInstruction account
|
||||||
|
/// 2. `[signer]` Admin account
|
||||||
|
AddSingleSignerInstruction {
|
||||||
|
/// Slot waiting time between vote period ending and this being eligible for execution
|
||||||
|
hold_up_time: u64,
|
||||||
|
|
||||||
|
/// Instruction
|
||||||
|
instruction: Instruction,
|
||||||
|
|
||||||
|
/// Position in instruction array
|
||||||
|
position: u8,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// [Requires Admin token]
|
||||||
|
/// Remove instruction from the Proposal
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Proposal account
|
||||||
|
/// 1. `[writable]` Proposal SingleSignerInstruction account
|
||||||
|
/// 2. `[signer]` Admin account
|
||||||
|
RemoveInstruction,
|
||||||
|
|
||||||
|
/// [Requires Admin token]
|
||||||
|
/// Update instruction hold up time in the Proposal
|
||||||
|
///
|
||||||
|
/// 0. `[]` Proposal account
|
||||||
|
/// 1. `[writable]` Proposal SingleSignerInstruction account
|
||||||
|
/// 2. `[signer]` Admin account
|
||||||
|
UpdateInstructionHoldUpTime {
|
||||||
|
/// Minimum waiting time in slots for an instruction to be executed after proposal is voted on
|
||||||
|
hold_up_time: u64,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// [Requires Admin token]
|
||||||
|
/// Cancels Proposal and moves it into Canceled
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Proposal account
|
||||||
|
/// 1. `[writable]` Admin account
|
||||||
|
CancelProposal,
|
||||||
|
|
||||||
|
/// [Requires Signatory token]
|
||||||
|
/// Burns signatory token, indicating you approve and sign off on moving this Proposal from Draft state to Voting state
|
||||||
|
/// The last Signatory token to be burned moves the state to Voting
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Proposal account
|
||||||
|
/// 1. `[writable]` Signatory account
|
||||||
|
/// 2. `[writable]` Signatory Mint account
|
||||||
|
/// 3. `[]` Token program account
|
||||||
|
/// 4. `[]` Clock sysvar
|
||||||
|
SignOffProposal,
|
||||||
|
|
||||||
|
/// Uses your voter weight (deposited Community or Council tokens) to cast a vote on a Proposal
|
||||||
|
/// By doing so you indicate you approve or disapprove of running the Proposal set of instructions
|
||||||
|
/// If you tip the consensus then the instructions can begin to be run after their hold up time
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Proposal account
|
||||||
|
/// 1. `[writable]` Voter Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||||
|
/// 2. `[writable]` Proposal Vote Record account. PDA seeds: ['governance',proposal,governing_token_owner]
|
||||||
|
/// 3. `[signer]` Vote Authority account
|
||||||
|
/// 4. `[]` Governance account
|
||||||
|
Vote {
|
||||||
|
/// Yes/No vote
|
||||||
|
vote: Vote,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Relinquish Vote removes voter weight from a Proposal and removes it from voter's active votes
|
||||||
|
/// If the Proposal is still being voted on then the voter's weight won't count towards the vote outcome
|
||||||
|
/// If the Proposal is already in decided state then the instruction has no impact on the Proposal
|
||||||
|
/// and only allows voters to prune their outstanding votes in case they wanted to withdraw Governing tokens from the Realm
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Proposal account
|
||||||
|
/// 1. `[writable]` Voter Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||||
|
/// 2. `[writable]` Proposal Vote Record account. PDA seeds: ['governance',proposal,governing_token_owner]
|
||||||
|
/// 3. `[signer]` Vote Authority account
|
||||||
|
RelinquishVote,
|
||||||
|
|
||||||
|
/// Executes an instruction in the Proposal
|
||||||
|
/// Anybody can execute transaction once Proposal has been voted Yes and transaction_hold_up time has passed
|
||||||
|
/// The actual instruction being executed will be signed by Governance PDA
|
||||||
|
/// For example to execute Program upgrade the ProgramGovernance PDA would be used as the singer
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Proposal account
|
||||||
|
/// 1. `[writable]` Instruction account you wish to execute
|
||||||
|
/// 2. `[]` Program being invoked account
|
||||||
|
/// 3. `[]` Governance account (PDA)
|
||||||
|
/// 4. `[]` Clock sysvar
|
||||||
|
/// 5+ Any extra accounts that are part of the instruction, in order
|
||||||
|
Execute,
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
//! A Governance program for the Solana blockchain.
|
||||||
|
|
||||||
|
pub mod entrypoint;
|
||||||
|
pub mod error;
|
||||||
|
pub mod instruction;
|
||||||
|
pub mod processor;
|
||||||
|
pub mod state;
|
||||||
|
|
||||||
|
// Export current sdk types for downstream users building with a different sdk version
|
||||||
|
pub use solana_program;
|
||||||
|
|
||||||
|
solana_program::declare_id!("Governance111111111111111111111111111111111");
|
|
@ -0,0 +1,14 @@
|
||||||
|
//! Instruction processor
|
||||||
|
|
||||||
|
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
|
||||||
|
|
||||||
|
use crate::error::GovernanceError;
|
||||||
|
|
||||||
|
/// Processes an instruction
|
||||||
|
pub fn process_instruction(
|
||||||
|
_program_id: &Pubkey,
|
||||||
|
_accounts: &[AccountInfo],
|
||||||
|
_input: &[u8],
|
||||||
|
) -> ProgramResult {
|
||||||
|
Err(GovernanceError::InvalidInstruction.into())
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
//! State enumerations
|
||||||
|
|
||||||
|
/// Defines all Governance accounts types
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum GovernanceAccountType {
|
||||||
|
/// Default uninitialized account state
|
||||||
|
Uninitialized,
|
||||||
|
|
||||||
|
/// Top level aggregation for governances with Community Token (and optional Council Token)
|
||||||
|
Realm,
|
||||||
|
|
||||||
|
/// Voter record for each voter and given governing token type within a Realm
|
||||||
|
VoterRecord,
|
||||||
|
|
||||||
|
/// Program Governance account
|
||||||
|
ProgramGovernance,
|
||||||
|
|
||||||
|
/// Proposal account for Governance account. A single Governance account can have multiple Proposal accounts
|
||||||
|
Proposal,
|
||||||
|
|
||||||
|
/// Vote record account for a given Proposal. Proposal can have 0..n voting records
|
||||||
|
ProposalVoteRecord,
|
||||||
|
|
||||||
|
/// Single Signer Instruction account which holds an instruction to execute for Proposal
|
||||||
|
SingleSignerInstruction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GovernanceAccountType {
|
||||||
|
fn default() -> Self {
|
||||||
|
GovernanceAccountType::Uninitialized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Vote with number of votes
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum VoteWeight {
|
||||||
|
/// Yes vote
|
||||||
|
Yes(u64),
|
||||||
|
|
||||||
|
/// No vote
|
||||||
|
No(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Governing Token type
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum GoverningTokenType {
|
||||||
|
/// Community token
|
||||||
|
Community,
|
||||||
|
/// Council token
|
||||||
|
Council,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What state a Proposal is in
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum ProposalState {
|
||||||
|
/// Draft - Proposal enters Draft state when it's created
|
||||||
|
Draft,
|
||||||
|
|
||||||
|
/// Signing - The Proposal is being signed by Signatories. Proposal enters the state when first Signatory Sings and leaves it when last Signatory signs
|
||||||
|
Signing,
|
||||||
|
|
||||||
|
/// Taking votes
|
||||||
|
Voting,
|
||||||
|
|
||||||
|
/// Voting ended with success
|
||||||
|
Succeeded,
|
||||||
|
|
||||||
|
/// Voting completed and now instructions are being execute. Proposal enter this state when first instruction is executed and leaves when the last instruction is executed
|
||||||
|
Executing,
|
||||||
|
|
||||||
|
/// Completed
|
||||||
|
Completed,
|
||||||
|
|
||||||
|
/// Cancelled
|
||||||
|
Cancelled,
|
||||||
|
|
||||||
|
/// Defeated
|
||||||
|
Defeated,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ProposalState {
|
||||||
|
fn default() -> Self {
|
||||||
|
ProposalState::Draft
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
//! Program accounts
|
||||||
|
|
||||||
|
pub mod enums;
|
||||||
|
pub mod program_governance;
|
||||||
|
pub mod proposal;
|
||||||
|
pub mod proposal_vote_record;
|
||||||
|
pub mod realm;
|
||||||
|
pub mod single_signer_instruction;
|
||||||
|
pub mod voter_record;
|
|
@ -0,0 +1,33 @@
|
||||||
|
//! Program Governance Account
|
||||||
|
|
||||||
|
use solana_program::pubkey::Pubkey;
|
||||||
|
|
||||||
|
use super::enums::GovernanceAccountType;
|
||||||
|
|
||||||
|
/// Program Governance Account
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
pub struct ProgramGovernance {
|
||||||
|
/// Account type
|
||||||
|
pub account_type: GovernanceAccountType,
|
||||||
|
|
||||||
|
/// Voting threshold in % required to tip the vote
|
||||||
|
/// It's the percentage of tokens out of the entire pool of governance tokens eligible to vote
|
||||||
|
pub vote_threshold: u8,
|
||||||
|
|
||||||
|
/// Minimum % of tokens for a governance token owner to be able to create a proposal
|
||||||
|
/// It's the percentage of tokens out of the entire pool of governance tokens eligible to vote
|
||||||
|
pub token_threshold_to_create_proposal: u8,
|
||||||
|
|
||||||
|
/// Minimum waiting time in slots for an instruction to be executed after proposal is voted on
|
||||||
|
pub min_instruction_hold_up_time: u64,
|
||||||
|
|
||||||
|
/// Program ID that is governed by this Governance
|
||||||
|
pub program: Pubkey,
|
||||||
|
|
||||||
|
/// Time limit in slots for proposal to be open for voting
|
||||||
|
pub max_voting_time: u64,
|
||||||
|
|
||||||
|
/// Running count of proposals
|
||||||
|
pub proposal_count: u32,
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
//! Proposal Account
|
||||||
|
|
||||||
|
use solana_program::{epoch_schedule::Slot, pubkey::Pubkey};
|
||||||
|
|
||||||
|
use super::enums::{GovernanceAccountType, GoverningTokenType, ProposalState};
|
||||||
|
|
||||||
|
/// Governance Proposal
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Proposal {
|
||||||
|
/// Governance account type
|
||||||
|
pub account_type: GovernanceAccountType,
|
||||||
|
|
||||||
|
/// Governance account the Proposal belongs to
|
||||||
|
pub governance: Pubkey,
|
||||||
|
|
||||||
|
/// Mint that creates signatory tokens of this Proposal
|
||||||
|
/// If there are outstanding signatory tokens, then cannot leave draft state. Signatories must burn tokens (ie agree
|
||||||
|
/// to move instruction to voting state) and bring mint to net 0 tokens outstanding. Each signatory gets 1 (serves as flag)
|
||||||
|
pub signatory_mint: Pubkey,
|
||||||
|
|
||||||
|
/// Admin ownership mint. One token is minted, can be used to grant admin status to a new person
|
||||||
|
pub admin_mint: Pubkey,
|
||||||
|
|
||||||
|
/// Indicates which Governing Token is used to vote on the Proposal
|
||||||
|
/// Whether the general Community token owners or the Council tokens owners vote on this Proposal
|
||||||
|
pub voting_token_type: GoverningTokenType,
|
||||||
|
|
||||||
|
/// Current state of the proposal
|
||||||
|
pub state: ProposalState,
|
||||||
|
|
||||||
|
/// Total signatory tokens minted, for use comparing to supply remaining during draft period
|
||||||
|
pub total_signatory_tokens_minted: u64,
|
||||||
|
|
||||||
|
/// Link to proposal's description
|
||||||
|
pub description_link: String,
|
||||||
|
|
||||||
|
/// Proposal name
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// When the Proposal ended voting - this will also be when the set was defeated or began executing naturally
|
||||||
|
pub voting_ended_at: Option<Slot>,
|
||||||
|
|
||||||
|
/// When the Proposal began voting
|
||||||
|
pub voting_began_at: Option<Slot>,
|
||||||
|
|
||||||
|
/// when the Proposal entered draft state
|
||||||
|
pub created_at: Option<Slot>,
|
||||||
|
|
||||||
|
/// when the Proposal entered completed state, also when execution ended naturally.
|
||||||
|
pub completed_at: Option<Slot>,
|
||||||
|
|
||||||
|
/// when the Proposal entered deleted state
|
||||||
|
pub deleted_at: Option<Slot>,
|
||||||
|
|
||||||
|
/// The number of the instructions already executed
|
||||||
|
pub number_of_executed_instructions: u8,
|
||||||
|
|
||||||
|
/// The number of instructions included in the proposal
|
||||||
|
pub number_of_instructions: u8,
|
||||||
|
|
||||||
|
/// Array of pubkeys pointing at Proposal instructions, up to 5
|
||||||
|
pub instruction: Vec<Pubkey>,
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
//! Proposal Vote Record Account
|
||||||
|
|
||||||
|
use solana_program::pubkey::Pubkey;
|
||||||
|
|
||||||
|
use super::enums::{GovernanceAccountType, VoteWeight};
|
||||||
|
|
||||||
|
/// Proposal Vote Record
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
pub struct ProposalVoteRecord {
|
||||||
|
/// Governance account type
|
||||||
|
pub account_type: GovernanceAccountType,
|
||||||
|
|
||||||
|
/// Proposal account
|
||||||
|
pub proposal: Pubkey,
|
||||||
|
|
||||||
|
/// The user who casted this vote
|
||||||
|
/// This is the Governing Token Owner who deposited governing tokens into the Realm
|
||||||
|
pub governing_token_owner: Pubkey,
|
||||||
|
|
||||||
|
/// Voter's vote: Yes/No and amount
|
||||||
|
pub vote: Option<VoteWeight>,
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
//! Realm Account
|
||||||
|
|
||||||
|
use solana_program::pubkey::Pubkey;
|
||||||
|
|
||||||
|
use super::enums::GovernanceAccountType;
|
||||||
|
|
||||||
|
/// Governance Realm Account
|
||||||
|
/// Account PDA seeds" ['governance', name]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Realm {
|
||||||
|
/// Governance account type
|
||||||
|
pub account_type: GovernanceAccountType,
|
||||||
|
|
||||||
|
/// Community mint
|
||||||
|
pub community_mint: Pubkey,
|
||||||
|
|
||||||
|
/// Council mint
|
||||||
|
pub council_mint: Option<Pubkey>,
|
||||||
|
|
||||||
|
/// Governance Realm name
|
||||||
|
pub name: String,
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
//! SingleSignerInstruction Account
|
||||||
|
|
||||||
|
use solana_program::instruction::Instruction;
|
||||||
|
|
||||||
|
use super::enums::GovernanceAccountType;
|
||||||
|
|
||||||
|
/// Account for an instruction to be executed for Proposal
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SingleSignerInstruction {
|
||||||
|
/// Governance Account type
|
||||||
|
pub account_type: GovernanceAccountType,
|
||||||
|
|
||||||
|
/// Minimum waiting time in slots for the instruction to be executed once proposal is voted on
|
||||||
|
pub hold_up_time: u64,
|
||||||
|
|
||||||
|
/// Instruction to execute
|
||||||
|
/// The instruction will be signed by Governance PDA the Proposal belongs to
|
||||||
|
// For example for ProgramGovernance the instruction to upgrade program will be signed by ProgramGovernance PDA
|
||||||
|
pub instruction: Instruction,
|
||||||
|
|
||||||
|
/// Executed flag
|
||||||
|
pub executed: bool,
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
//! Voter Record Account
|
||||||
|
|
||||||
|
use solana_program::pubkey::Pubkey;
|
||||||
|
|
||||||
|
use super::enums::{GovernanceAccountType, GoverningTokenType};
|
||||||
|
|
||||||
|
/// Governance Voter Record
|
||||||
|
/// Account PDA seeds: ['governance', realm, token_mint, token_owner ]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct VoterRecord {
|
||||||
|
/// Governance account type
|
||||||
|
pub account_type: GovernanceAccountType,
|
||||||
|
|
||||||
|
/// The Realm the VoterRecord belongs to
|
||||||
|
pub realm: Pubkey,
|
||||||
|
|
||||||
|
/// The type of the Governing Token the VoteRecord is for
|
||||||
|
pub token_type: GoverningTokenType,
|
||||||
|
|
||||||
|
/// The owner (either single or multisig) of the deposited governing SPL Tokens
|
||||||
|
/// This is who can authorize a withdrawal
|
||||||
|
pub token_owner: Pubkey,
|
||||||
|
|
||||||
|
/// The amount of governing tokens deposited into the Realm
|
||||||
|
/// This amount is the voter weight used when voting on proposals
|
||||||
|
pub token_deposit_amount: u64,
|
||||||
|
|
||||||
|
/// A single account that is allowed to operate governance with the deposited governing tokens
|
||||||
|
/// It's delegated to by the token owner
|
||||||
|
pub vote_authority: Pubkey,
|
||||||
|
|
||||||
|
/// The number of active votes cast by voter
|
||||||
|
pub active_votes_count: u8,
|
||||||
|
|
||||||
|
/// The total number of votes cast by the voter
|
||||||
|
pub total_votes_count: u8,
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 203 KiB |
Binary file not shown.
After Width: | Height: | Size: 225 KiB |
|
@ -12,18 +12,18 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
borsh = "0.7.1"
|
borsh = "0.8"
|
||||||
borsh-derive = "0.8.1"
|
borsh-derive = "0.8.1"
|
||||||
num-derive = "0.3"
|
num-derive = "0.3"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
uint = "0.8"
|
uint = "0.8"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = "0.10"
|
proptest = "0.10"
|
||||||
solana-program-test = "1.6.2"
|
solana-program-test = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -1,35 +1,42 @@
|
||||||
//! Approximation calculations
|
//! Approximation calculations
|
||||||
|
|
||||||
use {
|
use {
|
||||||
num_traits::{CheckedAdd, CheckedDiv, One, Zero},
|
num_traits::{CheckedShl, CheckedShr, PrimInt},
|
||||||
std::cmp::Eq,
|
std::cmp::Ordering,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SQRT_ITERATIONS: u8 = 50;
|
/// Calculate square root of the given number
|
||||||
|
///
|
||||||
|
/// Code lovingly adapted from the excellent work at:
|
||||||
|
/// https://github.com/derekdreery/integer-sqrt-rs
|
||||||
|
///
|
||||||
|
/// The algorithm is based on the implementation in:
|
||||||
|
/// https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_(base_2)
|
||||||
|
pub fn sqrt<T: PrimInt + CheckedShl + CheckedShr>(radicand: T) -> Option<T> {
|
||||||
|
match radicand.cmp(&T::zero()) {
|
||||||
|
Ordering::Less => return None, // fail for less than 0
|
||||||
|
Ordering::Equal => return Some(T::zero()), // do nothing for 0
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
/// Perform square root
|
// Compute bit, the largest power of 4 <= n
|
||||||
pub fn sqrt<T: CheckedAdd + CheckedDiv + One + Zero + Eq + Copy>(radicand: T) -> Option<T> {
|
let max_shift: u32 = T::zero().leading_zeros() - 1;
|
||||||
if radicand == T::zero() {
|
let shift: u32 = (max_shift - radicand.leading_zeros()) & !1;
|
||||||
return Some(T::zero());
|
let mut bit = T::one().checked_shl(shift)?;
|
||||||
}
|
|
||||||
// A good initial guess is the average of the interval that contains the
|
let mut n = radicand;
|
||||||
// input number. For all numbers, that will be between 1 and the given number.
|
let mut result = T::zero();
|
||||||
let one = T::one();
|
while bit != T::zero() {
|
||||||
let two = one.checked_add(&one)?;
|
let result_with_bit = result.checked_add(&bit)?;
|
||||||
let mut guess = radicand.checked_div(&two)?.checked_add(&one)?;
|
if n >= result_with_bit {
|
||||||
let mut last_guess = guess;
|
n = n.checked_sub(&result_with_bit)?;
|
||||||
for _ in 0..SQRT_ITERATIONS {
|
result = result.checked_shr(1)?.checked_add(&bit)?;
|
||||||
// x_k+1 = (x_k + radicand / x_k) / 2
|
|
||||||
guess = last_guess
|
|
||||||
.checked_add(&radicand.checked_div(&last_guess)?)?
|
|
||||||
.checked_div(&two)?;
|
|
||||||
if last_guess == guess {
|
|
||||||
break;
|
|
||||||
} else {
|
} else {
|
||||||
last_guess = guess;
|
result = result.checked_shr(1)?;
|
||||||
}
|
}
|
||||||
|
bit = bit.checked_shr(2)?;
|
||||||
}
|
}
|
||||||
Some(guess)
|
Some(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -43,7 +43,16 @@ pub enum MathInstruction {
|
||||||
/// The multipier
|
/// The multipier
|
||||||
multiplier: u64,
|
multiplier: u64,
|
||||||
},
|
},
|
||||||
/// Multiply two float valies
|
/// Divide two u64 values
|
||||||
|
///
|
||||||
|
/// No accounts required for this instruction
|
||||||
|
U64Divide {
|
||||||
|
/// The dividend
|
||||||
|
dividend: u64,
|
||||||
|
/// The divisor
|
||||||
|
divisor: u64,
|
||||||
|
},
|
||||||
|
/// Multiply two float values
|
||||||
///
|
///
|
||||||
/// No accounts required for this instruction
|
/// No accounts required for this instruction
|
||||||
F32Multiply {
|
F32Multiply {
|
||||||
|
@ -52,7 +61,7 @@ pub enum MathInstruction {
|
||||||
/// The multipier
|
/// The multipier
|
||||||
multiplier: f32,
|
multiplier: f32,
|
||||||
},
|
},
|
||||||
/// Divide two float valies
|
/// Divide two float values
|
||||||
///
|
///
|
||||||
/// No accounts required for this instruction
|
/// No accounts required for this instruction
|
||||||
F32Divide {
|
F32Divide {
|
||||||
|
@ -114,6 +123,17 @@ pub fn u64_multiply(multiplicand: u64, multiplier: u64) -> Instruction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create PreciseSquareRoot instruction
|
||||||
|
pub fn u64_divide(dividend: u64, divisor: u64) -> Instruction {
|
||||||
|
Instruction {
|
||||||
|
program_id: id(),
|
||||||
|
accounts: vec![],
|
||||||
|
data: MathInstruction::U64Divide { dividend, divisor }
|
||||||
|
.try_to_vec()
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create PreciseSquareRoot instruction
|
/// Create PreciseSquareRoot instruction
|
||||||
pub fn f32_multiply(multiplicand: f32, multiplier: f32) -> Instruction {
|
pub fn f32_multiply(multiplicand: f32, multiplier: f32) -> Instruction {
|
||||||
Instruction {
|
Instruction {
|
||||||
|
|
|
@ -3,9 +3,36 @@
|
||||||
use {
|
use {
|
||||||
crate::{approximations::sqrt, instruction::MathInstruction, precise_number::PreciseNumber},
|
crate::{approximations::sqrt, instruction::MathInstruction, precise_number::PreciseNumber},
|
||||||
borsh::BorshDeserialize,
|
borsh::BorshDeserialize,
|
||||||
solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey},
|
solana_program::{
|
||||||
|
account_info::AccountInfo, entrypoint::ProgramResult, log::sol_log_compute_units, msg,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// u64_multiply
|
||||||
|
#[inline(never)]
|
||||||
|
fn u64_multiply(multiplicand: u64, multiplier: u64) -> u64 {
|
||||||
|
multiplicand * multiplier
|
||||||
|
}
|
||||||
|
|
||||||
|
/// u64_divide
|
||||||
|
#[inline(never)]
|
||||||
|
fn u64_divide(dividend: u64, divisor: u64) -> u64 {
|
||||||
|
dividend / divisor
|
||||||
|
}
|
||||||
|
|
||||||
|
/// f32_multiply
|
||||||
|
#[inline(never)]
|
||||||
|
fn f32_multiply(multiplicand: f32, multiplier: f32) -> f32 {
|
||||||
|
multiplicand * multiplier
|
||||||
|
}
|
||||||
|
|
||||||
|
/// f32_divide
|
||||||
|
#[inline(never)]
|
||||||
|
fn f32_divide(dividend: f32, divisor: f32) -> f32 {
|
||||||
|
dividend / divisor
|
||||||
|
}
|
||||||
|
|
||||||
/// Instruction processor
|
/// Instruction processor
|
||||||
pub fn process_instruction(
|
pub fn process_instruction(
|
||||||
_program_id: &Pubkey,
|
_program_id: &Pubkey,
|
||||||
|
@ -17,19 +44,25 @@ pub fn process_instruction(
|
||||||
MathInstruction::PreciseSquareRoot { radicand } => {
|
MathInstruction::PreciseSquareRoot { radicand } => {
|
||||||
msg!("Calculating square root using PreciseNumber");
|
msg!("Calculating square root using PreciseNumber");
|
||||||
let radicand = PreciseNumber::new(radicand as u128).unwrap();
|
let radicand = PreciseNumber::new(radicand as u128).unwrap();
|
||||||
|
sol_log_compute_units();
|
||||||
let result = radicand.sqrt().unwrap().to_imprecise().unwrap() as u64;
|
let result = radicand.sqrt().unwrap().to_imprecise().unwrap() as u64;
|
||||||
|
sol_log_compute_units();
|
||||||
msg!("{}", result);
|
msg!("{}", result);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
MathInstruction::SquareRootU64 { radicand } => {
|
MathInstruction::SquareRootU64 { radicand } => {
|
||||||
msg!("Calculating u64 square root");
|
msg!("Calculating u64 square root");
|
||||||
|
sol_log_compute_units();
|
||||||
let result = sqrt(radicand).unwrap();
|
let result = sqrt(radicand).unwrap();
|
||||||
|
sol_log_compute_units();
|
||||||
msg!("{}", result);
|
msg!("{}", result);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
MathInstruction::SquareRootU128 { radicand } => {
|
MathInstruction::SquareRootU128 { radicand } => {
|
||||||
msg!("Calculating u128 square root");
|
msg!("Calculating u128 square root");
|
||||||
|
sol_log_compute_units();
|
||||||
let result = sqrt(radicand).unwrap();
|
let result = sqrt(radicand).unwrap();
|
||||||
|
sol_log_compute_units();
|
||||||
msg!("{}", result);
|
msg!("{}", result);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -38,7 +71,17 @@ pub fn process_instruction(
|
||||||
multiplier,
|
multiplier,
|
||||||
} => {
|
} => {
|
||||||
msg!("Calculating U64 Multiply");
|
msg!("Calculating U64 Multiply");
|
||||||
let result = multiplicand * multiplier;
|
sol_log_compute_units();
|
||||||
|
let result = u64_multiply(multiplicand, multiplier);
|
||||||
|
sol_log_compute_units();
|
||||||
|
msg!("{}", result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
MathInstruction::U64Divide { dividend, divisor } => {
|
||||||
|
msg!("Calculating U64 Divide");
|
||||||
|
sol_log_compute_units();
|
||||||
|
let result = u64_divide(dividend, divisor);
|
||||||
|
sol_log_compute_units();
|
||||||
msg!("{}", result);
|
msg!("{}", result);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -47,13 +90,17 @@ pub fn process_instruction(
|
||||||
multiplier,
|
multiplier,
|
||||||
} => {
|
} => {
|
||||||
msg!("Calculating f32 Multiply");
|
msg!("Calculating f32 Multiply");
|
||||||
let result = multiplicand * multiplier;
|
sol_log_compute_units();
|
||||||
|
let result = f32_multiply(multiplicand, multiplier);
|
||||||
|
sol_log_compute_units();
|
||||||
msg!("{}", result as u64);
|
msg!("{}", result as u64);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
MathInstruction::F32Divide { dividend, divisor } => {
|
MathInstruction::F32Divide { dividend, divisor } => {
|
||||||
msg!("Calculating f32 Divide");
|
msg!("Calculating f32 Divide");
|
||||||
let result = dividend / divisor;
|
sol_log_compute_units();
|
||||||
|
let result = f32_divide(dividend, divisor);
|
||||||
|
sol_log_compute_units();
|
||||||
msg!("{}", result as u64);
|
msg!("{}", result as u64);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ async fn test_sqrt_u128() {
|
||||||
let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
|
let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
|
||||||
|
|
||||||
// Dial down the BPF compute budget to detect if the operation gets bloated in the future
|
// Dial down the BPF compute budget to detect if the operation gets bloated in the future
|
||||||
pc.set_bpf_compute_max_units(5_500);
|
pc.set_bpf_compute_max_units(4_000);
|
||||||
|
|
||||||
let (mut banks_client, payer, recent_blockhash) = pc.start().await;
|
let (mut banks_client, payer, recent_blockhash) = pc.start().await;
|
||||||
|
|
||||||
|
@ -78,8 +78,7 @@ async fn test_sqrt_u128() {
|
||||||
async fn test_sqrt_u128_max() {
|
async fn test_sqrt_u128_max() {
|
||||||
let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
|
let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
|
||||||
|
|
||||||
// This is pretty big too!
|
pc.set_bpf_compute_max_units(6_000);
|
||||||
pc.set_bpf_compute_max_units(90_000);
|
|
||||||
|
|
||||||
let (mut banks_client, payer, recent_blockhash) = pc.start().await;
|
let (mut banks_client, payer, recent_blockhash) = pc.start().await;
|
||||||
|
|
||||||
|
@ -103,6 +102,20 @@ async fn test_u64_multiply() {
|
||||||
banks_client.process_transaction(transaction).await.unwrap();
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_u64_divide() {
|
||||||
|
let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
|
||||||
|
|
||||||
|
pc.set_bpf_compute_max_units(1650);
|
||||||
|
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = pc.start().await;
|
||||||
|
|
||||||
|
let mut transaction =
|
||||||
|
Transaction::new_with_payer(&[instruction::u64_divide(3, 1)], Some(&payer.pubkey()));
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_f32_multiply() {
|
async fn test_f32_multiply() {
|
||||||
let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
|
let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "spl-memo"
|
name = "spl-memo"
|
||||||
version = "3.0.0"
|
version = "3.0.1"
|
||||||
description = "Solana Program Library Memo"
|
description = "Solana Program Library Memo"
|
||||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||||
repository = "https://github.com/solana-labs/solana-program-library"
|
repository = "https://github.com/solana-labs/solana-program-library"
|
||||||
|
@ -12,11 +12,11 @@ no-entrypoint = []
|
||||||
test-bpf = []
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.6.2"
|
solana-program-test = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -117,6 +117,7 @@ async fn test_memo_signing() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
#[ignore]
|
||||||
async fn test_memo_compute_limits() {
|
async fn test_memo_compute_limits() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
src/target/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
target
|
||||||
|
.vscode
|
||||||
|
hfuzz*
|
||||||
|
third_party/
|
||||||
|
|
||||||
|
*/node_modules
|
||||||
|
js/dist
|
||||||
|
js/lib
|
||||||
|
js/docs
|
||||||
|
js/src/secret.ts
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Name Service program
|
||||||
|
|
||||||
|
A spl program for issuing and managing ownership of: domain names, Solana Pubkeys, URLs, twitter handles, ipfs cid's, metadata, etc..
|
||||||
|
|
||||||
|
This program provides an interface and implementation that third parties can
|
||||||
|
utilize to create and use their own version of a name service of any kind.
|
||||||
|
|
||||||
|
Full documentation is available at https://spl.solana.com/name-service
|
||||||
|
|
||||||
|
JavaScript binding are available in the `./js` directory.
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"name": "spl-name-service",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/solana-labs/solana-program-library"
|
||||||
|
},
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsc && node --trace-warnings dist/test.js",
|
||||||
|
"build": "tsc",
|
||||||
|
"prepublish": "tsc",
|
||||||
|
"lint": "yarn pretty && eslint .",
|
||||||
|
"lint:fix": "yarn pretty:fix && eslint . --fix",
|
||||||
|
"pretty": "prettier --check 'src/*.[jt]s'",
|
||||||
|
"pretty:fix": "prettier --write 'src/*.[jt]s'",
|
||||||
|
"doc": "yarn typedoc src/index.ts"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tsconfig/recommended": "^1.0.1",
|
||||||
|
"@types/bs58": "^4.0.1",
|
||||||
|
"@types/node": "^14.14.20",
|
||||||
|
"babel-eslint": "^10.1.0",
|
||||||
|
"eslint": "^7.17.0",
|
||||||
|
"eslint-plugin-import": "^2.22.1",
|
||||||
|
"nodemon": "^2.0.7",
|
||||||
|
"prettier": "^2.2.1",
|
||||||
|
"save-dev": "0.0.1-security",
|
||||||
|
"ts-node": "^9.1.1",
|
||||||
|
"tslib": "^2.2.0",
|
||||||
|
"typedoc": "^0.20.35",
|
||||||
|
"typescript": "^4.1.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@project-serum/sol-wallet-adapter": "^0.1.5",
|
||||||
|
"@solana/spl-token": "0.1.3",
|
||||||
|
"@solana/web3.js": "^1.2.6",
|
||||||
|
"bip32": "^2.0.6",
|
||||||
|
"bn.js": "^5.1.3",
|
||||||
|
"@bonfida/borsh-js": "^0.3.1",
|
||||||
|
"bs58": "4.0.1",
|
||||||
|
"buffer-layout": "^1.2.0",
|
||||||
|
"core-util-is": "^1.0.2",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
|
"crypto-ts": "^1.0.2",
|
||||||
|
"fs": "^0.0.1-security",
|
||||||
|
"tweetnacl": "^1.0.3",
|
||||||
|
"webpack-dev-server": "^3.11.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
import {
|
||||||
|
Account,
|
||||||
|
Connection,
|
||||||
|
PublicKey,
|
||||||
|
SystemProgram,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { Numberu64 } from "./utils";
|
||||||
|
import { updateInstruction, transferInstruction, createInstruction, deleteInstruction } from "./instructions";
|
||||||
|
import { createHash, HashOptions } from 'crypto';
|
||||||
|
import { getHashedName, getNameAccountKey, getNameOwner, Numberu32 } from ".";
|
||||||
|
import { hash } from "tweetnacl";
|
||||||
|
import { NameRegistryState } from "./state";
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
export const NAME_PROGRAM_ID = new PublicKey(
|
||||||
|
"Gh9eN9nDuS3ysmAkKf4QJ6yBzf3YNqsn6MD8Ms3TsXmA"
|
||||||
|
);
|
||||||
|
export const HASH_PREFIX = "SPL Name Service";
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a name account with the given rent budget, allocated space, owner and class.
|
||||||
|
*
|
||||||
|
* @param connection The solana connection object to the RPC node
|
||||||
|
* @param name The name of the new account
|
||||||
|
* @param space The space in bytes allocated to the account
|
||||||
|
* @param payerKey The allocation cost payer
|
||||||
|
* @param nameOwner The pubkey to be set as owner of the new name account
|
||||||
|
* @param lamports The budget to be set for the name account. If not specified, it'll be the minimum for rent exemption
|
||||||
|
* @param nameClass The class of this new name
|
||||||
|
* @param parentName The parent name of the new name. If specified its owner needs to sign
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function createNameRegistry(
|
||||||
|
connection: Connection,
|
||||||
|
name: string,
|
||||||
|
space: number,
|
||||||
|
payerKey: PublicKey,
|
||||||
|
nameOwner: PublicKey,
|
||||||
|
lamports?: number,
|
||||||
|
nameClass?: PublicKey,
|
||||||
|
parentName?: PublicKey,
|
||||||
|
): Promise<TransactionInstruction> {
|
||||||
|
let hashed_name = await getHashedName(name);
|
||||||
|
let nameAccountKey = await getNameAccountKey(hashed_name, nameClass, parentName);
|
||||||
|
|
||||||
|
let balance = lamports
|
||||||
|
? lamports
|
||||||
|
: await connection.getMinimumBalanceForRentExemption(space);
|
||||||
|
|
||||||
|
let nameParentOwner: PublicKey | undefined;
|
||||||
|
if (!!parentName) {
|
||||||
|
let parentAccount = await getNameOwner(connection, parentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
let createNameInstr = createInstruction(
|
||||||
|
NAME_PROGRAM_ID,
|
||||||
|
SystemProgram.programId,
|
||||||
|
nameAccountKey,
|
||||||
|
nameOwner,
|
||||||
|
payerKey,
|
||||||
|
hashed_name,
|
||||||
|
new Numberu64(balance),
|
||||||
|
new Numberu32(space),
|
||||||
|
nameClass,
|
||||||
|
parentName,
|
||||||
|
nameParentOwner
|
||||||
|
);
|
||||||
|
|
||||||
|
return createNameInstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrite the data of the given name registry.
|
||||||
|
*
|
||||||
|
* @param connection The solana connection object to the RPC node
|
||||||
|
* @param name The name of the name registry to update
|
||||||
|
* @param offset The offset to which the data should be written into the registry
|
||||||
|
* @param input_data The data to be written
|
||||||
|
* @param nameClass The class of this name, if it exsists
|
||||||
|
* @param nameParent The parent name of this name, if it exists
|
||||||
|
*/
|
||||||
|
export async function updateNameRegistryData(
|
||||||
|
connection: Connection,
|
||||||
|
name: string,
|
||||||
|
offset: number,
|
||||||
|
input_data: Buffer,
|
||||||
|
nameClass?: PublicKey,
|
||||||
|
nameParent?: PublicKey,
|
||||||
|
): Promise<TransactionInstruction> {
|
||||||
|
let hashed_name = await getHashedName(name);
|
||||||
|
let nameAccountKey = await getNameAccountKey(hashed_name, nameClass, nameParent);
|
||||||
|
|
||||||
|
let signer: PublicKey;
|
||||||
|
if (!!nameClass) {
|
||||||
|
signer = nameClass;
|
||||||
|
} else {
|
||||||
|
signer = (await NameRegistryState.retrieve(connection, nameAccountKey)).owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updateInstr = updateInstruction(
|
||||||
|
NAME_PROGRAM_ID,
|
||||||
|
nameAccountKey,
|
||||||
|
new Numberu32(offset),
|
||||||
|
input_data,
|
||||||
|
signer
|
||||||
|
);
|
||||||
|
|
||||||
|
return updateInstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cahnge the owner of a given name account.
|
||||||
|
*
|
||||||
|
* @param connection The solana connection object to the RPC node
|
||||||
|
* @param name The name of the name account
|
||||||
|
* @param newOwner The new owner to be set
|
||||||
|
* @param curentNameOwner the current name Owner
|
||||||
|
* @param nameClass The class of this name, if it exsists
|
||||||
|
* @param nameParent The parent name of this name, if it exists
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function transferNameOwnership(
|
||||||
|
connection: Connection,
|
||||||
|
name: string,
|
||||||
|
newOwner: PublicKey,
|
||||||
|
currentNameOwner: PublicKey,
|
||||||
|
nameClass?: PublicKey,
|
||||||
|
nameParent?: PublicKey,
|
||||||
|
): Promise<TransactionInstruction> {
|
||||||
|
let hashed_name = await getHashedName(name);
|
||||||
|
let nameAccountKey = await getNameAccountKey(hashed_name, nameClass, nameParent);
|
||||||
|
|
||||||
|
let curentNameOwner: PublicKey;
|
||||||
|
if (!!nameClass) {
|
||||||
|
curentNameOwner = nameClass;
|
||||||
|
} else {
|
||||||
|
curentNameOwner = (await NameRegistryState.retrieve(connection, nameAccountKey)).owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
let transferInstr = transferInstruction(
|
||||||
|
NAME_PROGRAM_ID,
|
||||||
|
nameAccountKey,
|
||||||
|
newOwner,
|
||||||
|
curentNameOwner,
|
||||||
|
nameClass
|
||||||
|
);
|
||||||
|
|
||||||
|
return transferInstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the name account and transfer the rent to the target.
|
||||||
|
*
|
||||||
|
* @param connection The solana connection object to the RPC node
|
||||||
|
* @param name The name of the name account
|
||||||
|
* @param refundTargetKey The refund destination address
|
||||||
|
* @param nameClass The class of this name, if it exsists
|
||||||
|
* @param nameParent The parent name of this name, if it exists
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function deleteNameRegistry(
|
||||||
|
connection: Connection,
|
||||||
|
name: string,
|
||||||
|
refundTargetKey: PublicKey,
|
||||||
|
nameClass?: PublicKey,
|
||||||
|
nameParent?: PublicKey,
|
||||||
|
): Promise<TransactionInstruction> {
|
||||||
|
let hashed_name = await getHashedName(name);
|
||||||
|
let nameAccountKey = await getNameAccountKey(hashed_name, nameClass, nameParent);
|
||||||
|
|
||||||
|
let nameOwner: PublicKey;
|
||||||
|
if (!!nameClass) {
|
||||||
|
nameOwner = nameClass;
|
||||||
|
} else {
|
||||||
|
nameOwner = (await NameRegistryState.retrieve(connection, nameAccountKey)).owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
let changeAuthoritiesInstr = deleteInstruction(
|
||||||
|
NAME_PROGRAM_ID,
|
||||||
|
nameAccountKey,
|
||||||
|
refundTargetKey,
|
||||||
|
nameOwner
|
||||||
|
);
|
||||||
|
|
||||||
|
return changeAuthoritiesInstr;
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./utils";
|
||||||
|
export * from "./bindings";
|
|
@ -0,0 +1,204 @@
|
||||||
|
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
|
||||||
|
import { Schema, serialize } from "@bonfida/borsh-js";
|
||||||
|
import { Numberu64, Numberu32 } from "./utils";
|
||||||
|
|
||||||
|
export function createInstruction(
|
||||||
|
nameProgramId: PublicKey,
|
||||||
|
systemProgramId: PublicKey,
|
||||||
|
nameKey: PublicKey,
|
||||||
|
nameOwnerKey: PublicKey,
|
||||||
|
payerKey: PublicKey,
|
||||||
|
hashed_name: Buffer,
|
||||||
|
lamports: Numberu64,
|
||||||
|
space: Numberu32,
|
||||||
|
nameClassKey?: PublicKey,
|
||||||
|
nameParent?: PublicKey,
|
||||||
|
nameParentOwner?: PublicKey
|
||||||
|
): TransactionInstruction {
|
||||||
|
|
||||||
|
let buffers = [
|
||||||
|
Buffer.from(Int8Array.from([0])),
|
||||||
|
new Numberu32(hashed_name.length).toBuffer(),
|
||||||
|
hashed_name,
|
||||||
|
lamports.toBuffer(),
|
||||||
|
space.toBuffer(),
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = Buffer.concat(buffers);
|
||||||
|
|
||||||
|
let keys = [
|
||||||
|
{
|
||||||
|
pubkey: systemProgramId,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pubkey: payerKey,
|
||||||
|
isSigner: true,
|
||||||
|
isWritable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pubkey: nameKey,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pubkey: nameOwnerKey,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!!nameClassKey) {
|
||||||
|
keys.push({
|
||||||
|
pubkey: nameClassKey,
|
||||||
|
isSigner: true,
|
||||||
|
isWritable: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
keys.push({
|
||||||
|
pubkey: new PublicKey(Buffer.alloc(32)),
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!!nameParent) {
|
||||||
|
keys.push({
|
||||||
|
pubkey: nameParent,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
keys.push({
|
||||||
|
pubkey: new PublicKey(Buffer.alloc(32)),
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!!nameParentOwner) {
|
||||||
|
keys.push({
|
||||||
|
pubkey: nameParentOwner,
|
||||||
|
isSigner: true,
|
||||||
|
isWritable: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TransactionInstruction({
|
||||||
|
keys,
|
||||||
|
programId: nameProgramId,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateInstruction(
|
||||||
|
nameProgramId: PublicKey,
|
||||||
|
nameAccountKey: PublicKey,
|
||||||
|
offset: Numberu32,
|
||||||
|
input_data: Buffer,
|
||||||
|
nameUpdateSigner: PublicKey,
|
||||||
|
): TransactionInstruction {
|
||||||
|
let buffers = [
|
||||||
|
Buffer.from(Int8Array.from([1])),
|
||||||
|
offset.toBuffer(),
|
||||||
|
new Numberu32(input_data.length).toBuffer(),
|
||||||
|
input_data
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = Buffer.concat(buffers);
|
||||||
|
let keys = [
|
||||||
|
{
|
||||||
|
pubkey: nameAccountKey,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pubkey: nameUpdateSigner,
|
||||||
|
isSigner: true,
|
||||||
|
isWritable: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return new TransactionInstruction({
|
||||||
|
keys,
|
||||||
|
programId: nameProgramId,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transferInstruction(
|
||||||
|
nameProgramId: PublicKey,
|
||||||
|
nameAccountKey: PublicKey,
|
||||||
|
newOwnerKey: PublicKey,
|
||||||
|
currentNameOwnerKey: PublicKey,
|
||||||
|
nameClassKey?: PublicKey,
|
||||||
|
): TransactionInstruction {
|
||||||
|
let buffers = [
|
||||||
|
Buffer.from(Int8Array.from([2])),
|
||||||
|
newOwnerKey.toBuffer()
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = Buffer.concat(buffers);
|
||||||
|
|
||||||
|
let keys = [
|
||||||
|
{
|
||||||
|
pubkey: nameAccountKey,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pubkey: currentNameOwnerKey,
|
||||||
|
isSigner: true,
|
||||||
|
isWritable: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!!nameClassKey) {
|
||||||
|
keys.push({
|
||||||
|
pubkey: nameClassKey,
|
||||||
|
isSigner: true,
|
||||||
|
isWritable: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TransactionInstruction({
|
||||||
|
keys,
|
||||||
|
programId: nameProgramId,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteInstruction(
|
||||||
|
nameProgramId: PublicKey,
|
||||||
|
nameAccountKey: PublicKey,
|
||||||
|
refundTargetKey: PublicKey,
|
||||||
|
nameOwnerKey: PublicKey,
|
||||||
|
): TransactionInstruction {
|
||||||
|
let buffers = [
|
||||||
|
Buffer.from(Int8Array.from([3])),
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = Buffer.concat(buffers);
|
||||||
|
let keys = [
|
||||||
|
{
|
||||||
|
pubkey: nameAccountKey,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pubkey: nameOwnerKey,
|
||||||
|
isSigner: true,
|
||||||
|
isWritable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pubkey: refundTargetKey,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return new TransactionInstruction({
|
||||||
|
keys,
|
||||||
|
programId: nameProgramId,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { PublicKey, Connection } from "@solana/web3.js";
|
||||||
|
import { Schema, deserializeUnchecked } from "@bonfida/borsh-js";
|
||||||
|
|
||||||
|
export class NameRegistryState {
|
||||||
|
parentName: PublicKey;
|
||||||
|
owner: PublicKey;
|
||||||
|
class: PublicKey;
|
||||||
|
data: Buffer;
|
||||||
|
|
||||||
|
static schema: Schema = new Map([
|
||||||
|
[
|
||||||
|
NameRegistryState,
|
||||||
|
{
|
||||||
|
kind: 'struct',
|
||||||
|
fields: [
|
||||||
|
['parentName', [32]],
|
||||||
|
['owner', [32]],
|
||||||
|
['class', [32]],
|
||||||
|
['data', ['u8']],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
constructor(obj: {
|
||||||
|
parentName: Uint8Array;
|
||||||
|
owner: Uint8Array;
|
||||||
|
class: Uint8Array;
|
||||||
|
data: Uint8Array;
|
||||||
|
}) {
|
||||||
|
this.parentName = new PublicKey(obj.parentName);
|
||||||
|
this.owner = new PublicKey(obj.owner);
|
||||||
|
this.class = new PublicKey(obj.class);
|
||||||
|
this.data = Buffer.from(obj.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async retrieve(
|
||||||
|
connection: Connection,
|
||||||
|
nameAccountKey: PublicKey,
|
||||||
|
): Promise<NameRegistryState> {
|
||||||
|
let nameAccount = await connection.getAccountInfo(
|
||||||
|
nameAccountKey,
|
||||||
|
'processed',
|
||||||
|
);
|
||||||
|
if (!nameAccount) {
|
||||||
|
throw new Error('Invalid name account provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
let res: NameRegistryState = deserializeUnchecked(
|
||||||
|
this.schema,
|
||||||
|
NameRegistryState,
|
||||||
|
nameAccount.data,
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { serialize } from "@bonfida/borsh-js";
|
||||||
|
import { Connection, Account, PublicKey, AccountInfo } from "@solana/web3.js";
|
||||||
|
import { transferNameOwnership, updateNameRegistryData, createNameRegistry, deleteNameRegistry } from "./bindings";
|
||||||
|
import { readFile } from "fs/promises";
|
||||||
|
import { Numberu64, signAndSendTransactionInstructions } from "./utils";
|
||||||
|
import { sign } from "tweetnacl";
|
||||||
|
import { getHashedName, getNameAccountKey, Numberu32 } from ".";
|
||||||
|
import { NameRegistryState } from "./state";
|
||||||
|
|
||||||
|
const ENDPOINT = 'https://devnet.solana.com/';
|
||||||
|
// const ENDPOINT = 'https://solana-api.projectserum.com/';
|
||||||
|
|
||||||
|
export async function test() {
|
||||||
|
let connection = new Connection(ENDPOINT);
|
||||||
|
let secretKey = JSON.parse(
|
||||||
|
(await readFile('/home/lcchy-work/.config/solana/id_devnet.json')).toString()
|
||||||
|
);
|
||||||
|
let adminAccount = new Account(secretKey);
|
||||||
|
|
||||||
|
let root_name = ".sol";
|
||||||
|
|
||||||
|
// let create_instruction = await createNameRegistry(
|
||||||
|
// connection,
|
||||||
|
// root_name,
|
||||||
|
// 1000,
|
||||||
|
// adminAccount.publicKey,
|
||||||
|
// adminAccount.publicKey,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// console.log(
|
||||||
|
// await signAndSendTransactionInstructions(
|
||||||
|
// connection,
|
||||||
|
// [adminAccount],
|
||||||
|
// adminAccount,
|
||||||
|
// [create_instruction]
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
|
||||||
|
|
||||||
|
// let input_data = Buffer.from("Du");
|
||||||
|
// let updateInstruction = await updateNameRegistryData(
|
||||||
|
// connection,
|
||||||
|
// root_name,
|
||||||
|
// 0,
|
||||||
|
// input_data,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// console.log(
|
||||||
|
// await signAndSendTransactionInstructions(
|
||||||
|
// connection,
|
||||||
|
// [adminAccount],
|
||||||
|
// adminAccount,
|
||||||
|
// [updateInstruction]
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
|
||||||
|
// let transferInstruction = await transferNameOwnership(
|
||||||
|
// connection,
|
||||||
|
// root_name,
|
||||||
|
// adminAccount.publicKey,
|
||||||
|
// adminAccount.publicKey,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// console.log(
|
||||||
|
// await signAndSendTransactionInstructions(
|
||||||
|
// connection,
|
||||||
|
// [adminAccount],
|
||||||
|
// adminAccount,
|
||||||
|
// [transferInstruction]
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
|
||||||
|
// let deleteInstruction = await deleteNameRegistry(
|
||||||
|
// connection,
|
||||||
|
// root_name,
|
||||||
|
// adminAccount.publicKey
|
||||||
|
// );
|
||||||
|
|
||||||
|
// console.log(
|
||||||
|
// await signAndSendTransactionInstructions(
|
||||||
|
// connection,
|
||||||
|
// [adminAccount],
|
||||||
|
// adminAccount,
|
||||||
|
// [deleteInstruction]
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
|
||||||
|
let hashed_root_name = await getHashedName(root_name);
|
||||||
|
let nameAccountKey = await getNameAccountKey(hashed_root_name);
|
||||||
|
console.log(await NameRegistryState.retrieve(connection, nameAccountKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
test();
|
|
@ -0,0 +1,132 @@
|
||||||
|
import {
|
||||||
|
PublicKey,
|
||||||
|
TransactionInstruction,
|
||||||
|
Connection,
|
||||||
|
Account,
|
||||||
|
Transaction,
|
||||||
|
AccountInfo,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import assert from "assert";
|
||||||
|
import BN from "bn.js";
|
||||||
|
import { createHash } from "crypto";
|
||||||
|
import { HASH_PREFIX, NAME_PROGRAM_ID } from ".";
|
||||||
|
import { NameRegistryState} from "./state";
|
||||||
|
|
||||||
|
export class Numberu32 extends BN {
|
||||||
|
/**
|
||||||
|
* Convert to Buffer representation
|
||||||
|
*/
|
||||||
|
toBuffer(): Buffer {
|
||||||
|
const a = super.toArray().reverse();
|
||||||
|
const b = Buffer.from(a);
|
||||||
|
if (b.length === 4) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
assert(b.length < 4, "Numberu32 too large");
|
||||||
|
|
||||||
|
const zeroPad = Buffer.alloc(4);
|
||||||
|
b.copy(zeroPad);
|
||||||
|
return zeroPad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a Numberu64 from Buffer representation
|
||||||
|
*/
|
||||||
|
static fromBuffer(buffer): any {
|
||||||
|
assert(buffer.length === 4, `Invalid buffer length: ${buffer.length}`);
|
||||||
|
return new BN(
|
||||||
|
[...buffer]
|
||||||
|
.reverse()
|
||||||
|
.map((i) => `00${i.toString(16)}`.slice(-2))
|
||||||
|
.join(""),
|
||||||
|
16
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Numberu64 extends BN {
|
||||||
|
/**
|
||||||
|
* Convert to Buffer representation
|
||||||
|
*/
|
||||||
|
toBuffer(): Buffer {
|
||||||
|
const a = super.toArray().reverse();
|
||||||
|
const b = Buffer.from(a);
|
||||||
|
if (b.length === 8) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
assert(b.length < 8, "Numberu64 too large");
|
||||||
|
|
||||||
|
const zeroPad = Buffer.alloc(8);
|
||||||
|
b.copy(zeroPad);
|
||||||
|
return zeroPad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a Numberu64 from Buffer representation
|
||||||
|
*/
|
||||||
|
static fromBuffer(buffer): any {
|
||||||
|
assert(buffer.length === 8, `Invalid buffer length: ${buffer.length}`);
|
||||||
|
return new BN(
|
||||||
|
[...buffer]
|
||||||
|
.reverse()
|
||||||
|
.map((i) => `00${i.toString(16)}`.slice(-2))
|
||||||
|
.join(""),
|
||||||
|
16
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const signAndSendTransactionInstructions = async (
|
||||||
|
// sign and send transaction
|
||||||
|
connection: Connection,
|
||||||
|
signers: Array<Account>,
|
||||||
|
feePayer: Account,
|
||||||
|
txInstructions: Array<TransactionInstruction>
|
||||||
|
): Promise<string> => {
|
||||||
|
const tx = new Transaction();
|
||||||
|
tx.feePayer = feePayer.publicKey;
|
||||||
|
signers.push(feePayer);
|
||||||
|
tx.add(...txInstructions);
|
||||||
|
return await connection.sendTransaction(tx, signers, {
|
||||||
|
preflightCommitment: "single",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getHashedName(
|
||||||
|
name: string
|
||||||
|
): Promise<Buffer> {
|
||||||
|
let input = HASH_PREFIX + name;
|
||||||
|
let buffer = createHash('sha256').update(input, 'utf8').digest();
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getNameAccountKey(
|
||||||
|
hashed_name: Buffer,
|
||||||
|
nameClass?: PublicKey,
|
||||||
|
nameParent?: PublicKey
|
||||||
|
): Promise<PublicKey> {
|
||||||
|
let seeds = [hashed_name];
|
||||||
|
if (!!nameClass) {
|
||||||
|
seeds.push(nameClass.toBuffer());
|
||||||
|
} else {
|
||||||
|
seeds.push(Buffer.alloc(32));
|
||||||
|
}
|
||||||
|
if (!!nameParent) {
|
||||||
|
seeds.push(nameParent.toBuffer());
|
||||||
|
} else {
|
||||||
|
seeds.push(Buffer.alloc(32));
|
||||||
|
}
|
||||||
|
let [nameAccountKey, _] = await PublicKey.findProgramAddress(
|
||||||
|
seeds,
|
||||||
|
NAME_PROGRAM_ID
|
||||||
|
);
|
||||||
|
return nameAccountKey
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getNameOwner(connection: Connection, nameAccountKey: PublicKey): Promise<NameRegistryState> {
|
||||||
|
let nameAccount = await connection.getAccountInfo(nameAccountKey);
|
||||||
|
if (!nameAccount) {
|
||||||
|
throw "Unable to find the given account."
|
||||||
|
}
|
||||||
|
return NameRegistryState.retrieve(connection, nameAccountKey); //TODO use borsh-js
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"extends": "@tsconfig/recommended/tsconfig.json",
|
||||||
|
"ts-node": {
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"paths": {
|
||||||
|
"*" : ["types/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"target": "es2019",
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"declaration": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"sourceMap": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"*": ["node_modules/*", "src/types/*"]
|
||||||
|
},
|
||||||
|
"resolveJsonModule": true
|
||||||
|
},
|
||||||
|
"include": ["src/*"],
|
||||||
|
"exclude": ["src/**/*.test.ts", "**/node_modules", "dist"]
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,29 @@
|
||||||
|
[package]
|
||||||
|
name = "spl-name-service"
|
||||||
|
description = "Solana Program Library Name Service"
|
||||||
|
version = "0.1.0"
|
||||||
|
repository = "https://github.com/solana-labs/solana-program-library"
|
||||||
|
authors = ["lcchy <lucas@bonfida.com>"]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
no-entrypoint = []
|
||||||
|
test-bpf = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
solana-program = "1.6.7"
|
||||||
|
num-traits = "0.2"
|
||||||
|
borsh = "0.8.1"
|
||||||
|
num-derive = "0.3.3"
|
||||||
|
thiserror = "1.0.24"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
solana-program-test = "1.6.7"
|
||||||
|
solana-sdk = "1.6.7"
|
||||||
|
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "lib"]
|
|
@ -0,0 +1,2 @@
|
||||||
|
[target.bpfel-unknown-unknown.dependencies.std]
|
||||||
|
features = []
|
|
@ -0,0 +1 @@
|
||||||
|
namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX
|
|
@ -0,0 +1,36 @@
|
||||||
|
use {
|
||||||
|
crate::error::NameServiceError,
|
||||||
|
crate::processor::Processor,
|
||||||
|
num_traits::FromPrimitive,
|
||||||
|
solana_program::{
|
||||||
|
account_info::AccountInfo, decode_error::DecodeError, entrypoint,
|
||||||
|
entrypoint::ProgramResult, msg, program_error::PrintProgramError, pubkey::Pubkey,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
entrypoint!(process_instruction);
|
||||||
|
|
||||||
|
pub fn process_instruction(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
instruction_data: &[u8],
|
||||||
|
) -> ProgramResult {
|
||||||
|
msg!("Entrypoint");
|
||||||
|
if let Err(error) = Processor::process_instruction(program_id, accounts, instruction_data) {
|
||||||
|
// catch the error so we can print it
|
||||||
|
error.print::<NameServiceError>();
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrintProgramError for NameServiceError {
|
||||||
|
fn print<E>(&self)
|
||||||
|
where
|
||||||
|
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
NameServiceError::OutOfSpace => msg!("Error: Registry is out of space!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
use {
|
||||||
|
num_derive::FromPrimitive,
|
||||||
|
solana_program::{decode_error::DecodeError, program_error::ProgramError},
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
|
||||||
|
pub enum NameServiceError {
|
||||||
|
#[error("Out of space")]
|
||||||
|
OutOfSpace,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type NameServiceResult = Result<(), NameServiceError>;
|
||||||
|
|
||||||
|
impl From<NameServiceError> for ProgramError {
|
||||||
|
fn from(e: NameServiceError) -> Self {
|
||||||
|
ProgramError::Custom(e as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DecodeError<T> for NameServiceError {
|
||||||
|
fn type_of() -> &'static str {
|
||||||
|
"NameServiceError"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
use {
|
||||||
|
borsh::{BorshDeserialize, BorshSerialize},
|
||||||
|
solana_program::{
|
||||||
|
instruction::{AccountMeta, Instruction},
|
||||||
|
program_error::ProgramError,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
system_program,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Instructions supported by the generic Name Registry program
|
||||||
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)]
|
||||||
|
pub enum NameRegistryInstruction {
|
||||||
|
/// Create an empty name record
|
||||||
|
///
|
||||||
|
/// The address of the name record (account #1) is a program-derived address with the following
|
||||||
|
/// seeds to ensure uniqueness:
|
||||||
|
/// * SHA256(HASH_PREFIX, `Create::name`)
|
||||||
|
/// * Account class (account #3)
|
||||||
|
/// * Parent name record address (account #4)
|
||||||
|
///
|
||||||
|
/// If this is a child record, the parent record's owner must approve by signing (account #5)
|
||||||
|
///
|
||||||
|
/// Accounts expected by this instruction:
|
||||||
|
/// 0. `[]` System program
|
||||||
|
/// 1. `[writeable, signer]` Funding account (must be a system account)
|
||||||
|
/// 2. `[writeable]` Name record to be created (program-derived address)
|
||||||
|
/// 3. `[]` Account owner (written into `NameRecordHeader::owner`)
|
||||||
|
/// 4. `[signer]` Account class (written into `NameRecordHeader::class`).
|
||||||
|
/// If `Pubkey::default()` then the `signer` bit is not required
|
||||||
|
/// 5. `[]` Parent name record (written into `NameRecordHeader::parent_name). `Pubkey::default()` is equivalent to no existing parent.
|
||||||
|
/// 6. `[signer]` Owner of the parent name record. Optional but needed if parent name different than default.
|
||||||
|
///
|
||||||
|
Create {
|
||||||
|
/// SHA256 of the (HASH_PREFIX + Name) of the record to create, hashing is done off-chain
|
||||||
|
hashed_name: Vec<u8>,
|
||||||
|
|
||||||
|
/// Number of lamports to fund the name record with
|
||||||
|
lamports: u64,
|
||||||
|
|
||||||
|
/// Number of bytes of memory to allocate in addition to the `NameRecordHeader`
|
||||||
|
space: u32,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Update the data in a name record
|
||||||
|
///
|
||||||
|
/// Accounts expected by this instruction:
|
||||||
|
/// * If account class is `Pubkey::default()`:
|
||||||
|
/// 0. `[writeable]` Name record to be updated
|
||||||
|
/// 1. `[signer]` Account owner
|
||||||
|
///
|
||||||
|
/// * If account class is not `Pubkey::default()`:
|
||||||
|
/// 0. `[writeable]` Name record to be updated
|
||||||
|
/// 1. `[signer]` Account class
|
||||||
|
///
|
||||||
|
Update { offset: u32, data: Vec<u8> },
|
||||||
|
|
||||||
|
/// Transfer ownership of a name record
|
||||||
|
///
|
||||||
|
/// Accounts expected by this instruction:
|
||||||
|
///
|
||||||
|
/// * If account class is `Pubkey::default()`:
|
||||||
|
/// 0. `[writeable]` Name record to be transferred
|
||||||
|
/// 1. `[signer]` Account owner
|
||||||
|
///
|
||||||
|
/// * If account class is not `Pubkey::default()`:
|
||||||
|
/// 0. `[writeable]` Name record to be transferred
|
||||||
|
/// 1. `[signer]` Account owner
|
||||||
|
/// 1. `[signer]` Account class
|
||||||
|
///
|
||||||
|
Transfer { new_owner: Pubkey },
|
||||||
|
|
||||||
|
/// Delete a name record.
|
||||||
|
///
|
||||||
|
/// Any lamports remaining in the name record will be transferred to the refund account (#2)
|
||||||
|
///
|
||||||
|
/// Accounts expected by this instruction:
|
||||||
|
/// 0. `[writeable]` Name record to be deleted
|
||||||
|
/// 1. `[signer]` Account owner
|
||||||
|
/// 2. `[writeable]` Refund account
|
||||||
|
///
|
||||||
|
Delete,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::clippy::too_many_arguments)]
|
||||||
|
pub fn create(
|
||||||
|
name_service_program_id: Pubkey,
|
||||||
|
instruction_data: NameRegistryInstruction,
|
||||||
|
name_account_key: Pubkey,
|
||||||
|
payer_key: Pubkey,
|
||||||
|
name_owner: Pubkey,
|
||||||
|
name_class_opt: Option<Pubkey>,
|
||||||
|
name_parent_opt: Option<Pubkey>,
|
||||||
|
name_parent_owner_opt: Option<Pubkey>,
|
||||||
|
) -> Result<Instruction, ProgramError> {
|
||||||
|
let data = instruction_data.try_to_vec().unwrap();
|
||||||
|
let mut accounts = vec![
|
||||||
|
AccountMeta::new_readonly(system_program::id(), false),
|
||||||
|
AccountMeta::new(payer_key, true),
|
||||||
|
AccountMeta::new(name_account_key, false),
|
||||||
|
AccountMeta::new_readonly(name_owner, false),
|
||||||
|
];
|
||||||
|
if let Some(name_class) = name_class_opt {
|
||||||
|
accounts.push(AccountMeta::new_readonly(name_class, true));
|
||||||
|
} else {
|
||||||
|
accounts.push(AccountMeta::new_readonly(Pubkey::default(), false));
|
||||||
|
}
|
||||||
|
if let Some(name_parent) = name_parent_opt {
|
||||||
|
accounts.push(AccountMeta::new_readonly(name_parent, false));
|
||||||
|
} else {
|
||||||
|
accounts.push(AccountMeta::new_readonly(Pubkey::default(), false));
|
||||||
|
}
|
||||||
|
if let Some(key) = name_parent_owner_opt {
|
||||||
|
accounts.push(AccountMeta::new_readonly(key, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Instruction {
|
||||||
|
program_id: name_service_program_id,
|
||||||
|
accounts,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(
|
||||||
|
name_service_program_id: Pubkey,
|
||||||
|
offset: u32,
|
||||||
|
data: Vec<u8>,
|
||||||
|
name_account_key: Pubkey,
|
||||||
|
name_update_signer: Pubkey,
|
||||||
|
) -> Result<Instruction, ProgramError> {
|
||||||
|
let instruction_data = NameRegistryInstruction::Update { offset, data };
|
||||||
|
let data = instruction_data.try_to_vec().unwrap();
|
||||||
|
let accounts = vec![
|
||||||
|
AccountMeta::new(name_account_key, false),
|
||||||
|
AccountMeta::new_readonly(name_update_signer, true),
|
||||||
|
];
|
||||||
|
|
||||||
|
Ok(Instruction {
|
||||||
|
program_id: name_service_program_id,
|
||||||
|
accounts,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer(
|
||||||
|
name_service_program_id: Pubkey,
|
||||||
|
new_owner: Pubkey,
|
||||||
|
name_account_key: Pubkey,
|
||||||
|
name_owner_key: Pubkey,
|
||||||
|
name_class_opt: Option<Pubkey>,
|
||||||
|
) -> Result<Instruction, ProgramError> {
|
||||||
|
let instruction_data = NameRegistryInstruction::Transfer { new_owner };
|
||||||
|
let data = instruction_data.try_to_vec().unwrap();
|
||||||
|
let mut accounts = vec![
|
||||||
|
AccountMeta::new(name_account_key, false),
|
||||||
|
AccountMeta::new_readonly(name_owner_key, true),
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Some(key) = name_class_opt {
|
||||||
|
accounts.push(AccountMeta::new_readonly(key, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Instruction {
|
||||||
|
program_id: name_service_program_id,
|
||||||
|
accounts,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(
|
||||||
|
name_service_program_id: Pubkey,
|
||||||
|
name_account_key: Pubkey,
|
||||||
|
name_owner_key: Pubkey,
|
||||||
|
refund_target: Pubkey,
|
||||||
|
) -> Result<Instruction, ProgramError> {
|
||||||
|
let instruction_data = NameRegistryInstruction::Delete;
|
||||||
|
let data = instruction_data.try_to_vec().unwrap();
|
||||||
|
let accounts = vec![
|
||||||
|
AccountMeta::new(name_account_key, false),
|
||||||
|
AccountMeta::new_readonly(name_owner_key, true),
|
||||||
|
AccountMeta::new(refund_target, false),
|
||||||
|
];
|
||||||
|
|
||||||
|
Ok(Instruction {
|
||||||
|
program_id: name_service_program_id,
|
||||||
|
accounts,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
#[cfg(not(feature = "no-entrypoint"))]
|
||||||
|
pub mod entrypoint;
|
||||||
|
pub mod error;
|
||||||
|
pub mod instruction;
|
||||||
|
pub mod processor;
|
||||||
|
pub mod state;
|
||||||
|
|
||||||
|
// Export current sdk types for downstream users building with a different sdk version
|
||||||
|
pub use solana_program;
|
||||||
|
|
||||||
|
solana_program::declare_id!("namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX");
|
|
@ -0,0 +1,242 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
instruction::NameRegistryInstruction,
|
||||||
|
state::get_seeds_and_key,
|
||||||
|
state::{write_data, NameRecordHeader},
|
||||||
|
},
|
||||||
|
borsh::BorshDeserialize,
|
||||||
|
solana_program::{
|
||||||
|
account_info::{next_account_info, AccountInfo},
|
||||||
|
entrypoint::ProgramResult,
|
||||||
|
msg,
|
||||||
|
program::{invoke, invoke_signed},
|
||||||
|
program_error::ProgramError,
|
||||||
|
program_pack::Pack,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
system_instruction,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Processor {}
|
||||||
|
|
||||||
|
impl Processor {
|
||||||
|
pub fn process_create(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
hashed_name: Vec<u8>,
|
||||||
|
lamports: u64,
|
||||||
|
space: u32,
|
||||||
|
) -> ProgramResult {
|
||||||
|
let accounts_iter = &mut accounts.iter();
|
||||||
|
|
||||||
|
let system_program = next_account_info(accounts_iter)?;
|
||||||
|
let payer_account = next_account_info(accounts_iter)?;
|
||||||
|
let name_account = next_account_info(accounts_iter)?;
|
||||||
|
let name_owner = next_account_info(accounts_iter)?;
|
||||||
|
let name_class = next_account_info(accounts_iter)?;
|
||||||
|
let parent_name_account = next_account_info(accounts_iter)?;
|
||||||
|
let parent_name_owner = next_account_info(accounts_iter).ok();
|
||||||
|
|
||||||
|
let (name_account_key, seeds) = get_seeds_and_key(
|
||||||
|
&program_id,
|
||||||
|
hashed_name,
|
||||||
|
Some(name_class.key),
|
||||||
|
Some(parent_name_account.key),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verifications
|
||||||
|
if name_account_key != *name_account.key {
|
||||||
|
msg!("The given name account is incorrect.");
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
if name_account.data.borrow().len() > 0 {
|
||||||
|
let name_record_header =
|
||||||
|
NameRecordHeader::unpack_from_slice(&name_account.data.borrow())?;
|
||||||
|
if name_record_header.owner != Pubkey::default() {
|
||||||
|
msg!("The given name account already exists.");
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *name_class.key != Pubkey::default() && !name_class.is_signer {
|
||||||
|
msg!("The given name class is not a signer.");
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
if *parent_name_account.key != Pubkey::default() {
|
||||||
|
if !parent_name_owner.unwrap().is_signer {
|
||||||
|
msg!("The given parent name account owner is not a signer.");
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
} else {
|
||||||
|
let parent_name_record_header =
|
||||||
|
NameRecordHeader::unpack_from_slice(&parent_name_account.data.borrow())?;
|
||||||
|
if &parent_name_record_header.owner != parent_name_owner.unwrap().key {
|
||||||
|
msg!("The given parent name account owner is not correct.");
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if name_owner.key == &Pubkey::default() {
|
||||||
|
msg!("The owner cannot be `Pubkey::default()`.");
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
if name_account.data.borrow().len() == 0 {
|
||||||
|
// Issue the name registry account
|
||||||
|
// The creation is done in three steps: transfer, allocate and assign, because
|
||||||
|
// one cannot `system_instruction::create` an account to which lamports have been transfered before.
|
||||||
|
invoke(
|
||||||
|
&system_instruction::transfer(&payer_account.key, &name_account_key, lamports),
|
||||||
|
&[
|
||||||
|
payer_account.clone(),
|
||||||
|
name_account.clone(),
|
||||||
|
system_program.clone(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
invoke_signed(
|
||||||
|
&system_instruction::allocate(&name_account_key, space as u64),
|
||||||
|
&[name_account.clone(), system_program.clone()],
|
||||||
|
&[&seeds.chunks(32).collect::<Vec<&[u8]>>()],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
invoke_signed(
|
||||||
|
&system_instruction::assign(name_account.key, &program_id),
|
||||||
|
&[name_account.clone(), system_program.clone()],
|
||||||
|
&[&seeds.chunks(32).collect::<Vec<&[u8]>>()],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name_state = NameRecordHeader {
|
||||||
|
parent_name: *parent_name_account.key,
|
||||||
|
owner: *name_owner.key,
|
||||||
|
class: *name_class.key,
|
||||||
|
};
|
||||||
|
|
||||||
|
name_state.pack_into_slice(&mut name_account.data.borrow_mut());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_update(accounts: &[AccountInfo], offset: u32, data: Vec<u8>) -> ProgramResult {
|
||||||
|
let accounts_iter = &mut accounts.iter();
|
||||||
|
|
||||||
|
let name_account = next_account_info(accounts_iter)?;
|
||||||
|
let name_update_signer = next_account_info(accounts_iter)?;
|
||||||
|
|
||||||
|
let name_record_header = NameRecordHeader::unpack_from_slice(&name_account.data.borrow())?;
|
||||||
|
|
||||||
|
// Verifications
|
||||||
|
if !name_update_signer.is_signer {
|
||||||
|
msg!("The given name class or owner is not a signer.");
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
if name_record_header.class != Pubkey::default()
|
||||||
|
&& *name_update_signer.key != name_record_header.class
|
||||||
|
{
|
||||||
|
msg!("The given name class account is incorrect.");
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
if name_record_header.class == Pubkey::default()
|
||||||
|
&& *name_update_signer.key != name_record_header.owner
|
||||||
|
{
|
||||||
|
msg!("The given name owner account is incorrect.");
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
write_data(name_account, &data, NameRecordHeader::LEN + offset as usize);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_transfer(accounts: &[AccountInfo], new_owner: Pubkey) -> ProgramResult {
|
||||||
|
let accounts_iter = &mut accounts.iter();
|
||||||
|
|
||||||
|
let name_account = next_account_info(accounts_iter)?;
|
||||||
|
let name_owner = next_account_info(accounts_iter)?;
|
||||||
|
let name_class_opt = next_account_info(accounts_iter).ok();
|
||||||
|
|
||||||
|
let mut name_record_header =
|
||||||
|
NameRecordHeader::unpack_from_slice(&name_account.data.borrow())?;
|
||||||
|
|
||||||
|
// Verifications
|
||||||
|
if !name_owner.is_signer || name_record_header.owner != *name_owner.key {
|
||||||
|
msg!("The given name owner is incorrect or not a signer.");
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
if name_record_header.class != Pubkey::default()
|
||||||
|
&& (name_class_opt.is_none()
|
||||||
|
|| name_record_header.class != *name_class_opt.unwrap().key
|
||||||
|
|| !name_class_opt.unwrap().is_signer)
|
||||||
|
{
|
||||||
|
msg!("The given name class account is incorrect or not a signer.");
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
name_record_header.owner = new_owner;
|
||||||
|
name_record_header
|
||||||
|
.pack_into_slice(&mut name_account.data.borrow_mut()[..NameRecordHeader::LEN]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_delete(accounts: &[AccountInfo]) -> ProgramResult {
|
||||||
|
let accounts_iter = &mut accounts.iter();
|
||||||
|
|
||||||
|
let name_account = next_account_info(accounts_iter)?;
|
||||||
|
let name_owner = next_account_info(accounts_iter)?;
|
||||||
|
let refund_target = next_account_info(accounts_iter)?;
|
||||||
|
|
||||||
|
let name_record_header = NameRecordHeader::unpack_from_slice(&name_account.data.borrow())?;
|
||||||
|
|
||||||
|
// Verifications
|
||||||
|
if !name_owner.is_signer || name_record_header.owner != *name_owner.key {
|
||||||
|
msg!("The given name owner is incorrect or not a signer.");
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite the data with zeroes
|
||||||
|
write_data(name_account, &vec![0; name_account.data_len()], 0);
|
||||||
|
|
||||||
|
// Close the account by transferring the rent sol
|
||||||
|
let source_amount: &mut u64 = &mut name_account.lamports.borrow_mut();
|
||||||
|
let dest_amount: &mut u64 = &mut refund_target.lamports.borrow_mut();
|
||||||
|
*dest_amount += *source_amount;
|
||||||
|
*source_amount = 0;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_instruction(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
instruction_data: &[u8],
|
||||||
|
) -> ProgramResult {
|
||||||
|
msg!("Beginning processing");
|
||||||
|
let instruction = NameRegistryInstruction::try_from_slice(instruction_data)
|
||||||
|
.map_err(|_| ProgramError::InvalidInstructionData)?;
|
||||||
|
msg!("Instruction unpack_from_sliceed");
|
||||||
|
|
||||||
|
match instruction {
|
||||||
|
NameRegistryInstruction::Create {
|
||||||
|
hashed_name,
|
||||||
|
lamports,
|
||||||
|
space,
|
||||||
|
} => {
|
||||||
|
msg!("Instruction: Create");
|
||||||
|
Processor::process_create(program_id, accounts, hashed_name, lamports, space)?;
|
||||||
|
}
|
||||||
|
NameRegistryInstruction::Update { offset, data } => {
|
||||||
|
msg!("Instruction: Update Data");
|
||||||
|
Processor::process_update(accounts, offset, data)?;
|
||||||
|
}
|
||||||
|
NameRegistryInstruction::Transfer { new_owner } => {
|
||||||
|
msg!("Instruction: Transfer Ownership");
|
||||||
|
Processor::process_transfer(accounts, new_owner)?;
|
||||||
|
}
|
||||||
|
NameRegistryInstruction::Delete => {
|
||||||
|
msg!("Instruction: Delete Name");
|
||||||
|
Processor::process_delete(accounts)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
use {
|
||||||
|
borsh::{BorshDeserialize, BorshSerialize},
|
||||||
|
solana_program::{
|
||||||
|
account_info::AccountInfo,
|
||||||
|
msg,
|
||||||
|
program_error::ProgramError,
|
||||||
|
program_pack::{IsInitialized, Pack, Sealed},
|
||||||
|
pubkey::Pubkey,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The data for a Name Registry account is always prefixed a `NameRecordHeader` structure.
|
||||||
|
///
|
||||||
|
/// The layout of the remaining bytes in the account data are determined by the record `class`
|
||||||
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)]
|
||||||
|
pub struct NameRecordHeader {
|
||||||
|
// Names are hierarchical. `parent_name` contains the account address of the parent
|
||||||
|
// name, or `Pubkey::default()` if no parent exists.
|
||||||
|
pub parent_name: Pubkey,
|
||||||
|
|
||||||
|
// The owner of this name
|
||||||
|
pub owner: Pubkey,
|
||||||
|
|
||||||
|
// The class of data this account represents (DNS record, twitter handle, SPL Token name/symbol, etc)
|
||||||
|
//
|
||||||
|
// If `Pubkey::default()` the data is unspecified.
|
||||||
|
pub class: Pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sealed for NameRecordHeader {}
|
||||||
|
|
||||||
|
impl Pack for NameRecordHeader {
|
||||||
|
const LEN: usize = 96;
|
||||||
|
|
||||||
|
fn pack_into_slice(&self, dst: &mut [u8]) {
|
||||||
|
let mut slice = dst;
|
||||||
|
self.serialize(&mut slice).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
|
||||||
|
let mut p = src;
|
||||||
|
NameRecordHeader::deserialize(&mut p).map_err(|_| {
|
||||||
|
msg!("Failed to deserialize name record");
|
||||||
|
ProgramError::InvalidAccountData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IsInitialized for NameRecordHeader {
|
||||||
|
fn is_initialized(&self) -> bool {
|
||||||
|
self.owner == Pubkey::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_data(account: &AccountInfo, input: &[u8], offset: usize) {
|
||||||
|
let mut account_data = account.data.borrow_mut();
|
||||||
|
account_data[offset..offset + input.len()].copy_from_slice(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
pub const HASH_PREFIX: &str = "SPL Name Service";
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
pub fn get_seeds_and_key(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
hashed_name: Vec<u8>, // Hashing is done off-chain
|
||||||
|
name_class_opt: Option<&Pubkey>,
|
||||||
|
parent_name_address_opt: Option<&Pubkey>,
|
||||||
|
) -> (Pubkey, Vec<u8>) {
|
||||||
|
// let hashed_name: Vec<u8> = hashv(&[(HASH_PREFIX.to_owned() + name).as_bytes()]).0.to_vec();
|
||||||
|
let mut seeds_vec: Vec<u8> = hashed_name;
|
||||||
|
|
||||||
|
let name_class = name_class_opt.cloned().unwrap_or_default();
|
||||||
|
|
||||||
|
for b in name_class.to_bytes().to_vec() {
|
||||||
|
seeds_vec.push(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent_name_address = parent_name_address_opt.cloned().unwrap_or_default();
|
||||||
|
|
||||||
|
for b in parent_name_address.to_bytes().to_vec() {
|
||||||
|
seeds_vec.push(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (name_account_key, bump) =
|
||||||
|
Pubkey::find_program_address(&seeds_vec.chunks(32).collect::<Vec<&[u8]>>(), program_id);
|
||||||
|
seeds_vec.push(bump);
|
||||||
|
|
||||||
|
(name_account_key, seeds_vec)
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
#![cfg(feature = "test-bpf")]
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use solana_program::{instruction::Instruction, program_pack::Pack, pubkey::Pubkey};
|
||||||
|
use solana_program_test::{processor, tokio, ProgramTest, ProgramTestContext};
|
||||||
|
|
||||||
|
use solana_program::hash::hashv;
|
||||||
|
use solana_sdk::{
|
||||||
|
signature::{Keypair, Signer},
|
||||||
|
transaction::Transaction,
|
||||||
|
transport::TransportError,
|
||||||
|
};
|
||||||
|
use spl_name_service::{
|
||||||
|
entrypoint::process_instruction,
|
||||||
|
instruction::{create, delete, transfer, update, NameRegistryInstruction},
|
||||||
|
state::{get_seeds_and_key, NameRecordHeader, HASH_PREFIX},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_name_service() {
|
||||||
|
// Create program and test environment
|
||||||
|
let program_id = Pubkey::from_str("XCWuBvfNamesXCWuBvfkegQfZyiNwAJb9Ss623VQ5DA").unwrap();
|
||||||
|
|
||||||
|
let program_test = ProgramTest::new(
|
||||||
|
"spl_name_service",
|
||||||
|
program_id,
|
||||||
|
processor!(process_instruction),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut ctx = program_test.start_with_context().await;
|
||||||
|
|
||||||
|
let root_name = ".sol";
|
||||||
|
let tld_class = Keypair::new();
|
||||||
|
let owner = Keypair::new();
|
||||||
|
|
||||||
|
let hashed_root_name: Vec<u8> = hashv(&[(HASH_PREFIX.to_owned() + root_name).as_bytes()])
|
||||||
|
.0
|
||||||
|
.to_vec();
|
||||||
|
let (root_name_account_key, _) = get_seeds_and_key(
|
||||||
|
&program_id,
|
||||||
|
hashed_root_name.clone(),
|
||||||
|
Some(&tld_class.pubkey()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let create_name_instruction = create(
|
||||||
|
program_id,
|
||||||
|
NameRegistryInstruction::Create {
|
||||||
|
hashed_name: hashed_root_name,
|
||||||
|
lamports: 1_000_000,
|
||||||
|
space: 1_000,
|
||||||
|
},
|
||||||
|
root_name_account_key,
|
||||||
|
ctx.payer.pubkey(),
|
||||||
|
owner.pubkey(),
|
||||||
|
Some(tld_class.pubkey()),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
sign_send_instruction(&mut ctx, create_name_instruction, vec![&tld_class])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let name_record_header = NameRecordHeader::unpack_from_slice(
|
||||||
|
&mut &ctx
|
||||||
|
.banks_client
|
||||||
|
.get_account(root_name_account_key)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
println!("Name Record Header: {:?}", name_record_header);
|
||||||
|
|
||||||
|
let name = "bonfida";
|
||||||
|
let sol_subdomains_class = Keypair::new();
|
||||||
|
|
||||||
|
let hashed_name: Vec<u8> = hashv(&[(HASH_PREFIX.to_owned() + name).as_bytes()])
|
||||||
|
.0
|
||||||
|
.to_vec();
|
||||||
|
let (name_account_key, _) = get_seeds_and_key(
|
||||||
|
&program_id,
|
||||||
|
hashed_name.clone(),
|
||||||
|
Some(&sol_subdomains_class.pubkey()),
|
||||||
|
Some(&root_name_account_key),
|
||||||
|
);
|
||||||
|
|
||||||
|
let create_name_instruction = create(
|
||||||
|
program_id,
|
||||||
|
NameRegistryInstruction::Create {
|
||||||
|
hashed_name,
|
||||||
|
lamports: 1_000_000,
|
||||||
|
space: 1_000,
|
||||||
|
},
|
||||||
|
name_account_key,
|
||||||
|
ctx.payer.pubkey(),
|
||||||
|
owner.pubkey(),
|
||||||
|
Some(sol_subdomains_class.pubkey()),
|
||||||
|
Some(root_name_account_key),
|
||||||
|
Some(owner.pubkey()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
sign_send_instruction(
|
||||||
|
&mut ctx,
|
||||||
|
create_name_instruction,
|
||||||
|
vec![&sol_subdomains_class, &owner],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let name_record_header = NameRecordHeader::unpack_from_slice(
|
||||||
|
&mut &ctx
|
||||||
|
.banks_client
|
||||||
|
.get_account(name_account_key)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
println!("Name Record Header: {:?}", name_record_header);
|
||||||
|
println!("SOl class {:?}", sol_subdomains_class.pubkey());
|
||||||
|
|
||||||
|
let data = "@Dudl".as_bytes().to_vec();
|
||||||
|
let update_instruction = update(
|
||||||
|
program_id,
|
||||||
|
0,
|
||||||
|
data,
|
||||||
|
name_account_key,
|
||||||
|
sol_subdomains_class.pubkey(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
sign_send_instruction(&mut ctx, update_instruction, vec![&sol_subdomains_class])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let name_record_header = NameRecordHeader::unpack_from_slice(
|
||||||
|
&mut &ctx
|
||||||
|
.banks_client
|
||||||
|
.get_account(name_account_key)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
println!("Name Record Header: {:?}", name_record_header);
|
||||||
|
|
||||||
|
let transfer_instruction = transfer(
|
||||||
|
program_id,
|
||||||
|
ctx.payer.pubkey(),
|
||||||
|
name_account_key,
|
||||||
|
owner.pubkey(),
|
||||||
|
Some(sol_subdomains_class.pubkey()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
sign_send_instruction(
|
||||||
|
&mut ctx,
|
||||||
|
transfer_instruction,
|
||||||
|
vec![&owner, &sol_subdomains_class],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let name_record_header = NameRecordHeader::unpack_from_slice(
|
||||||
|
&mut &ctx
|
||||||
|
.banks_client
|
||||||
|
.get_account(name_account_key)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
println!("Name Record Header: {:?}", name_record_header);
|
||||||
|
|
||||||
|
let delete_instruction = delete(
|
||||||
|
program_id,
|
||||||
|
name_account_key,
|
||||||
|
ctx.payer.pubkey(),
|
||||||
|
ctx.payer.pubkey(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
sign_send_instruction(&mut ctx, delete_instruction, vec![])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
pub async fn sign_send_instruction(
|
||||||
|
ctx: &mut ProgramTestContext,
|
||||||
|
instruction: Instruction,
|
||||||
|
signers: Vec<&Keypair>,
|
||||||
|
) -> Result<(), TransportError> {
|
||||||
|
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&ctx.payer.pubkey()));
|
||||||
|
let mut payer_signers = vec![&ctx.payer];
|
||||||
|
for s in signers {
|
||||||
|
payer_signers.push(s);
|
||||||
|
}
|
||||||
|
transaction.partial_sign(&payer_signers, ctx.last_blockhash);
|
||||||
|
ctx.banks_client.process_transaction(transaction).await
|
||||||
|
}
|
|
@ -16,12 +16,12 @@ borsh = "0.8.1"
|
||||||
borsh-derive = "0.8.1"
|
borsh-derive = "0.8.1"
|
||||||
num-derive = "0.3"
|
num-derive = "0.3"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-program-test = "1.6.2"
|
solana-program-test = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -7,14 +7,16 @@ repository = "https://github.com/solana-labs/solana-program-library"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
test-bpf = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arrayref = "0.3.6"
|
arrayref = "0.3.6"
|
||||||
solana-program = "1.6.2"
|
solana-program = "=1.6.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
solana-bpf-loader-program = "1.6.2"
|
solana-program-test = "=1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "=1.6.7"
|
||||||
solana_rbpf = "0.2"
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -1,180 +1,185 @@
|
||||||
use solana_bpf_loader_program::serialization::serialize_parameters;
|
// Program test does not support calling a raw program entrypoint, only `process_instruction`
|
||||||
use solana_program::{
|
#![cfg(feature = "test-bpf")]
|
||||||
bpf_loader, entrypoint::SUCCESS, program_error::ProgramError, pubkey::Pubkey,
|
|
||||||
|
use solana_program_test::*;
|
||||||
|
use solana_sdk::{
|
||||||
|
account::Account,
|
||||||
|
instruction::InstructionError,
|
||||||
|
instruction::{AccountMeta, Instruction},
|
||||||
|
pubkey::Pubkey,
|
||||||
|
signature::Signer,
|
||||||
|
transaction::{Transaction, TransactionError},
|
||||||
};
|
};
|
||||||
use solana_sdk::{account::AccountSharedData, keyed_account::KeyedAccount};
|
|
||||||
use spl_shared_memory::entrypoint;
|
|
||||||
|
|
||||||
// TODO: Rework `assert_instruction_count` test to use solana-program-test, avoiding the need to
|
#[tokio::test]
|
||||||
// link directly with the BPF VM
|
async fn assert_instruction_count() {
|
||||||
/*
|
|
||||||
fn load_program(name: &str) -> Vec<u8> {
|
|
||||||
let mut file =
|
|
||||||
File::open(&name).unwrap_or_else(|err| panic!("Unable to open {}: {}", name, err));
|
|
||||||
|
|
||||||
let mut program = Vec::new();
|
|
||||||
file.read_to_end(&mut program).unwrap();
|
|
||||||
program
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_program(
|
|
||||||
program_id: &Pubkey,
|
|
||||||
parameter_accounts: &[KeyedAccount],
|
|
||||||
instruction_data: &[u8],
|
|
||||||
) -> u64 {
|
|
||||||
let program_account = Account {
|
|
||||||
data: load_program("../../target/deploy/spl_shared_memory.so"),
|
|
||||||
..Account::default()
|
|
||||||
};
|
|
||||||
let loader_id = bpf_loader::id();
|
|
||||||
let mut invoke_context = MockInvokeContext::default();
|
|
||||||
let executable = EbpfVm::<solana_bpf_loader_program::BPFError>::create_executable_from_elf(
|
|
||||||
&&program_account.data,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let (mut vm, heap_region) = create_vm(
|
|
||||||
&loader_id,
|
|
||||||
executable.as_ref(),
|
|
||||||
parameter_accounts,
|
|
||||||
&mut invoke_context,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let mut parameter_bytes = serialize_parameters(
|
|
||||||
&loader_id,
|
|
||||||
program_id,
|
|
||||||
parameter_accounts,
|
|
||||||
&instruction_data,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
SUCCESS,
|
|
||||||
vm.execute_program(parameter_bytes.as_mut_slice(), &[], &[heap_region])
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
deserialize_parameters(&loader_id, parameter_accounts, ¶meter_bytes).unwrap();
|
|
||||||
vm.get_total_instruction_count()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn assert_instruction_count() {
|
|
||||||
const OFFSET: usize = 51;
|
const OFFSET: usize = 51;
|
||||||
const NUM_TO_SHARE: usize = 500;
|
const NUM_TO_SHARE: usize = 500;
|
||||||
let program_id = Pubkey::new_unique();
|
let program_id = Pubkey::new_unique();
|
||||||
let shared_key = Pubkey::new_unique();
|
let shared_key = Pubkey::new_unique();
|
||||||
let shared_account = Account::new_ref(u64::MAX, OFFSET + NUM_TO_SHARE * 2, &program_id);
|
|
||||||
|
|
||||||
// Send some data to share
|
let mut program_test = ProgramTest::new(
|
||||||
let parameter_accounts = vec![KeyedAccount::new(&shared_key, true, &shared_account)];
|
"spl_shared_memory", // Run the BPF version with `cargo test-bpf`
|
||||||
let content = vec![42; NUM_TO_SHARE];
|
program_id,
|
||||||
let mut instruction_data = OFFSET.to_le_bytes().to_vec();
|
None,
|
||||||
instruction_data.extend_from_slice(&content);
|
|
||||||
let share_count = run_program(&program_id, ¶meter_accounts[..], &instruction_data);
|
|
||||||
const BASELINE_COUNT: u64 = 1474; // 113 if NUM_TO_SHARE is 8
|
|
||||||
println!(
|
|
||||||
"BPF instructions executed {:?} (expected {:?})",
|
|
||||||
share_count, BASELINE_COUNT
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
program_test.add_account(
|
||||||
&shared_account.borrow().data[OFFSET..OFFSET + NUM_TO_SHARE],
|
shared_key,
|
||||||
content
|
Account {
|
||||||
|
lamports: 5000000000000,
|
||||||
|
data: vec![0_u8; NUM_TO_SHARE * 2],
|
||||||
|
owner: program_id,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
);
|
);
|
||||||
assert!(share_count <= BASELINE_COUNT);
|
program_test.set_bpf_compute_max_units(480);
|
||||||
}
|
let (mut banks_client, payer, recent_blockhash) = program_test.start().await;
|
||||||
*/
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_share_data() {
|
|
||||||
const OFFSET: usize = 51;
|
|
||||||
const NUM_TO_SHARE: usize = 500;
|
|
||||||
let program_id = Pubkey::new(&[0; 32]);
|
|
||||||
let shared_key = Pubkey::new_unique();
|
|
||||||
let shared_account = AccountSharedData::new_ref(u64::MAX, NUM_TO_SHARE * 2, &program_id);
|
|
||||||
|
|
||||||
// success
|
// success
|
||||||
let content = vec![42; NUM_TO_SHARE];
|
let content = vec![42; NUM_TO_SHARE];
|
||||||
let mut instruction_data = OFFSET.to_le_bytes().to_vec();
|
let mut instruction_data = OFFSET.to_le_bytes().to_vec();
|
||||||
instruction_data.extend_from_slice(&content);
|
instruction_data.extend_from_slice(&content);
|
||||||
let keyed_accounts = vec![KeyedAccount::new(&shared_key, true, &shared_account)];
|
let mut transaction = Transaction::new_with_payer(
|
||||||
let mut input = serialize_parameters(
|
&[Instruction::new_with_bytes(
|
||||||
&bpf_loader::id(),
|
program_id,
|
||||||
&program_id,
|
&instruction_data,
|
||||||
&keyed_accounts,
|
vec![AccountMeta::new(shared_key, false)],
|
||||||
&instruction_data,
|
)],
|
||||||
)
|
Some(&payer.pubkey()),
|
||||||
.unwrap();
|
);
|
||||||
assert_eq!(unsafe { entrypoint(input.as_mut_ptr()) }, SUCCESS);
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_helloworld() {
|
||||||
|
const OFFSET: usize = 51;
|
||||||
|
const NUM_TO_SHARE: usize = 500;
|
||||||
|
let program_id = Pubkey::new_unique();
|
||||||
|
let shared_key = Pubkey::new_unique();
|
||||||
|
|
||||||
|
let mut program_test = ProgramTest::new(
|
||||||
|
"spl_shared_memory", // Run the BPF version with `cargo test-bpf`
|
||||||
|
program_id,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
program_test.add_account(
|
||||||
|
shared_key,
|
||||||
|
Account {
|
||||||
|
lamports: 5000000000000,
|
||||||
|
data: vec![0_u8; NUM_TO_SHARE * 2],
|
||||||
|
owner: program_id,
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = program_test.start().await;
|
||||||
|
|
||||||
|
// success
|
||||||
|
let content = vec![42; NUM_TO_SHARE];
|
||||||
|
let mut instruction_data = OFFSET.to_le_bytes().to_vec();
|
||||||
|
instruction_data.extend_from_slice(&content);
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[Instruction::new_with_bytes(
|
||||||
|
program_id,
|
||||||
|
&instruction_data,
|
||||||
|
vec![AccountMeta::new(shared_key, false)],
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
// success zero offset
|
// success zero offset
|
||||||
let content = vec![42; NUM_TO_SHARE];
|
let content = vec![42; NUM_TO_SHARE];
|
||||||
let mut instruction_data = 0_usize.to_le_bytes().to_vec();
|
let mut instruction_data = 0_usize.to_le_bytes().to_vec();
|
||||||
instruction_data.extend_from_slice(&content);
|
instruction_data.extend_from_slice(&content);
|
||||||
let keyed_accounts = vec![KeyedAccount::new(&shared_key, true, &shared_account)];
|
let mut transaction = Transaction::new_with_payer(
|
||||||
let mut input = serialize_parameters(
|
&[Instruction::new_with_bytes(
|
||||||
&bpf_loader::id(),
|
program_id,
|
||||||
&program_id,
|
&instruction_data,
|
||||||
&keyed_accounts,
|
vec![AccountMeta::new(shared_key, false)],
|
||||||
&instruction_data,
|
)],
|
||||||
)
|
Some(&payer.pubkey()),
|
||||||
.unwrap();
|
);
|
||||||
assert_eq!(unsafe { entrypoint(input.as_mut_ptr()) }, SUCCESS);
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
// too few accounts
|
// too few accounts
|
||||||
let mut input =
|
let content = vec![42; NUM_TO_SHARE];
|
||||||
serialize_parameters(&bpf_loader::id(), &program_id, &[], &instruction_data).unwrap();
|
let mut instruction_data = OFFSET.to_le_bytes().to_vec();
|
||||||
|
instruction_data.extend_from_slice(&content);
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[Instruction::new_with_bytes(
|
||||||
|
program_id,
|
||||||
|
&instruction_data,
|
||||||
|
vec![],
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
let result = banks_client.process_transaction(transaction).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unsafe { entrypoint(input.as_mut_ptr()) },
|
result.unwrap_err().unwrap(),
|
||||||
u64::from(ProgramError::NotEnoughAccountKeys)
|
TransactionError::InstructionError(0, InstructionError::NotEnoughAccountKeys)
|
||||||
);
|
);
|
||||||
|
|
||||||
// too many accounts
|
// too many accounts
|
||||||
let keyed_accounts = vec![
|
let content = vec![42; NUM_TO_SHARE];
|
||||||
KeyedAccount::new(&shared_key, true, &shared_account),
|
let mut instruction_data = OFFSET.to_le_bytes().to_vec();
|
||||||
KeyedAccount::new(&shared_key, true, &shared_account),
|
instruction_data.extend_from_slice(&content);
|
||||||
];
|
let mut transaction = Transaction::new_with_payer(
|
||||||
let mut input = serialize_parameters(
|
&[Instruction::new_with_bytes(
|
||||||
&bpf_loader::id(),
|
program_id,
|
||||||
&program_id,
|
&instruction_data,
|
||||||
&keyed_accounts,
|
vec![
|
||||||
&instruction_data,
|
AccountMeta::new(shared_key, false),
|
||||||
)
|
AccountMeta::new(shared_key, false),
|
||||||
.unwrap();
|
],
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
let result = banks_client.process_transaction(transaction).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unsafe { entrypoint(input.as_mut_ptr()) },
|
result.unwrap_err().unwrap(),
|
||||||
u64::from(ProgramError::InvalidArgument)
|
TransactionError::InstructionError(0, InstructionError::InvalidArgument)
|
||||||
);
|
);
|
||||||
|
|
||||||
// account data too small
|
// account data too small
|
||||||
let keyed_accounts = vec![KeyedAccount::new(&shared_key, true, &shared_account)];
|
|
||||||
let content = vec![42; NUM_TO_SHARE * 10];
|
let content = vec![42; NUM_TO_SHARE * 10];
|
||||||
let mut instruction_data = OFFSET.to_le_bytes().to_vec();
|
let mut instruction_data = OFFSET.to_le_bytes().to_vec();
|
||||||
instruction_data.extend_from_slice(&content);
|
instruction_data.extend_from_slice(&content);
|
||||||
let mut input = serialize_parameters(
|
let mut transaction = Transaction::new_with_payer(
|
||||||
&bpf_loader::id(),
|
&[Instruction::new_with_bytes(
|
||||||
&program_id,
|
program_id,
|
||||||
&keyed_accounts,
|
&instruction_data,
|
||||||
&instruction_data,
|
vec![AccountMeta::new(shared_key, false)],
|
||||||
)
|
)],
|
||||||
.unwrap();
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
let result = banks_client.process_transaction(transaction).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unsafe { entrypoint(input.as_mut_ptr()) },
|
result.unwrap_err().unwrap(),
|
||||||
u64::from(ProgramError::AccountDataTooSmall)
|
TransactionError::InstructionError(0, InstructionError::AccountDataTooSmall)
|
||||||
);
|
);
|
||||||
|
|
||||||
// offset too large
|
// offset too large
|
||||||
let keyed_accounts = vec![KeyedAccount::new(&shared_key, true, &shared_account)];
|
|
||||||
let content = vec![42; NUM_TO_SHARE];
|
let content = vec![42; NUM_TO_SHARE];
|
||||||
let mut instruction_data = (OFFSET * 10).to_le_bytes().to_vec();
|
let mut instruction_data = (OFFSET * 10).to_le_bytes().to_vec();
|
||||||
instruction_data.extend_from_slice(&content);
|
instruction_data.extend_from_slice(&content);
|
||||||
let mut input = serialize_parameters(
|
let mut transaction = Transaction::new_with_payer(
|
||||||
&bpf_loader::id(),
|
&[Instruction::new_with_bytes(
|
||||||
&program_id,
|
program_id,
|
||||||
&keyed_accounts,
|
&instruction_data,
|
||||||
&instruction_data,
|
vec![AccountMeta::new(shared_key, false)],
|
||||||
)
|
)],
|
||||||
.unwrap();
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
let result = banks_client.process_transaction(transaction).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unsafe { entrypoint(input.as_mut_ptr()) },
|
result.unwrap_err().unwrap(),
|
||||||
u64::from(ProgramError::AccountDataTooSmall)
|
TransactionError::InstructionError(0, InstructionError::AccountDataTooSmall)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,21 +6,22 @@ homepage = "https://spl.solana.com/stake-pool"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
name = "spl-stake-pool-cli"
|
name = "spl-stake-pool-cli"
|
||||||
repository = "https://github.com/solana-labs/solana-program-library"
|
repository = "https://github.com/solana-labs/solana-program-library"
|
||||||
version = "2.0.1"
|
version = "0.2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
borsh = "0.8"
|
borsh = "0.8"
|
||||||
clap = "2.33.3"
|
clap = "2.33.3"
|
||||||
serde_json = "1.0.62"
|
serde_json = "1.0.62"
|
||||||
solana-account-decoder = "1.6.2"
|
solana-account-decoder = "1.6.7"
|
||||||
solana-clap-utils = "1.6.2"
|
solana-clap-utils = "1.6.7"
|
||||||
solana-cli-config = "1.6.2"
|
solana-cli-config = "1.6.7"
|
||||||
solana-client = "1.6.2"
|
solana-client = "1.6.7"
|
||||||
solana-logger = "1.6.2"
|
solana-logger = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
spl-stake-pool = { path="../program", features = [ "no-entrypoint" ] }
|
spl-associated-token-account = { version = "1.0", path="../../associated-token-account/program", features = [ "no-entrypoint" ] }
|
||||||
spl-token = { path="../../token/program", features = [ "no-entrypoint" ] }
|
spl-stake-pool = { version = "0.2", path="../program", features = [ "no-entrypoint" ] }
|
||||||
|
spl-token = { version = "3.1", path="../../token/program", features = [ "no-entrypoint" ] }
|
||||||
bs58 = "0.4.0"
|
bs58 = "0.4.0"
|
||||||
bincode = "1.3.1"
|
bincode = "1.3.1"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
|
|
@ -18,17 +18,17 @@ use {
|
||||||
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
pub(crate) fn get_stake_pool(
|
pub fn get_stake_pool(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
pool_address: &Pubkey,
|
stake_pool_address: &Pubkey,
|
||||||
) -> Result<StakePool, Error> {
|
) -> Result<StakePool, Error> {
|
||||||
let account_data = rpc_client.get_account_data(pool_address)?;
|
let account_data = rpc_client.get_account_data(stake_pool_address)?;
|
||||||
let stake_pool = StakePool::try_from_slice(account_data.as_slice())
|
let stake_pool = StakePool::try_from_slice(account_data.as_slice())
|
||||||
.map_err(|err| format!("Invalid stake pool {}: {}", pool_address, err))?;
|
.map_err(|err| format!("Invalid stake pool {}: {}", stake_pool_address, err))?;
|
||||||
Ok(stake_pool)
|
Ok(stake_pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_validator_list(
|
pub fn get_validator_list(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
validator_list_address: &Pubkey,
|
validator_list_address: &Pubkey,
|
||||||
) -> Result<ValidatorList, Error> {
|
) -> Result<ValidatorList, Error> {
|
||||||
|
@ -38,7 +38,7 @@ pub(crate) fn get_validator_list(
|
||||||
Ok(validator_list)
|
Ok(validator_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_token_account(
|
pub fn get_token_account(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
token_account_address: &Pubkey,
|
token_account_address: &Pubkey,
|
||||||
expected_token_mint: &Pubkey,
|
expected_token_mint: &Pubkey,
|
||||||
|
@ -58,7 +58,7 @@ pub(crate) fn get_token_account(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_token_mint(
|
pub fn get_token_mint(
|
||||||
rpc_client: &RpcClient,
|
rpc_client: &RpcClient,
|
||||||
token_mint_address: &Pubkey,
|
token_mint_address: &Pubkey,
|
||||||
) -> Result<spl_token::state::Mint, Error> {
|
) -> Result<spl_token::state::Mint, Error> {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "spl-stake-pool"
|
name = "spl-stake-pool"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
description = "Solana Program Library Stake Pool"
|
description = "Solana Program Library Stake Pool"
|
||||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||||
repository = "https://github.com/solana-labs/solana-program-library"
|
repository = "https://github.com/solana-labs/solana-program-library"
|
||||||
|
@ -19,17 +19,17 @@ num-traits = "0.2"
|
||||||
num_enum = "0.5.1"
|
num_enum = "0.5.1"
|
||||||
serde = "1.0.121"
|
serde = "1.0.121"
|
||||||
serde_derive = "1.0.103"
|
serde_derive = "1.0.103"
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
spl-math = { path = "../../libraries/math", features = [ "no-entrypoint" ] }
|
spl-math = { version = "0.1", path = "../../libraries/math", features = [ "no-entrypoint" ] }
|
||||||
spl-token = { path = "../../token/program", features = [ "no-entrypoint" ] }
|
spl-token = { version = "3.1", path = "../../token/program", features = [ "no-entrypoint" ] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
bincode = "1.3.1"
|
bincode = "1.3.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = "0.10"
|
proptest = "0.10"
|
||||||
solana-program-test = "1.6.2"
|
solana-program-test = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
solana-vote-program = "1.6.2"
|
solana-vote-program = "1.6.7"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -28,16 +28,16 @@ pub enum StakePoolError {
|
||||||
/// Token account is associated with the wrong mint.
|
/// Token account is associated with the wrong mint.
|
||||||
#[error("WrongAccountMint")]
|
#[error("WrongAccountMint")]
|
||||||
WrongAccountMint,
|
WrongAccountMint,
|
||||||
/// Wrong pool owner account.
|
/// Wrong pool manager account.
|
||||||
#[error("WrongOwner")]
|
#[error("WrongManager")]
|
||||||
WrongOwner,
|
WrongManager,
|
||||||
/// Required signature is missing.
|
/// Required signature is missing.
|
||||||
#[error("SignatureMissing")]
|
#[error("SignatureMissing")]
|
||||||
SignatureMissing,
|
SignatureMissing,
|
||||||
/// Invalid validator stake list account.
|
/// Invalid validator stake list account.
|
||||||
#[error("InvalidValidatorStakeList")]
|
#[error("InvalidValidatorStakeList")]
|
||||||
InvalidValidatorStakeList,
|
InvalidValidatorStakeList,
|
||||||
/// Invalid owner fee account.
|
/// Invalid manager fee account.
|
||||||
#[error("InvalidFeeAccount")]
|
#[error("InvalidFeeAccount")]
|
||||||
InvalidFeeAccount,
|
InvalidFeeAccount,
|
||||||
|
|
||||||
|
@ -79,6 +79,15 @@ pub enum StakePoolError {
|
||||||
/// The size of the given validator stake list does match the expected amount
|
/// The size of the given validator stake list does match the expected amount
|
||||||
#[error("UnexpectedValidatorListAccountSize")]
|
#[error("UnexpectedValidatorListAccountSize")]
|
||||||
UnexpectedValidatorListAccountSize,
|
UnexpectedValidatorListAccountSize,
|
||||||
|
/// Wrong pool staker account.
|
||||||
|
#[error("WrongStaker")]
|
||||||
|
WrongStaker,
|
||||||
|
/// Pool token supply is not zero on initialization
|
||||||
|
#[error("NonZeroPoolTokenSupply")]
|
||||||
|
NonZeroPoolTokenSupply,
|
||||||
|
/// The lamports in the validator stake account is not equal to the minimum
|
||||||
|
#[error("StakeLamportsNotEqualToMinimum")]
|
||||||
|
StakeLamportsNotEqualToMinimum,
|
||||||
}
|
}
|
||||||
impl From<StakePoolError> for ProgramError {
|
impl From<StakePoolError> for ProgramError {
|
||||||
fn from(e: StakePoolError) -> Self {
|
fn from(e: StakePoolError) -> Self {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -14,7 +14,10 @@ pub mod entrypoint;
|
||||||
|
|
||||||
// Export current sdk types for downstream users building with a different sdk version
|
// Export current sdk types for downstream users building with a different sdk version
|
||||||
pub use solana_program;
|
pub use solana_program;
|
||||||
use solana_program::pubkey::Pubkey;
|
use {
|
||||||
|
crate::stake_program::Meta,
|
||||||
|
solana_program::{native_token::LAMPORTS_PER_SOL, pubkey::Pubkey},
|
||||||
|
};
|
||||||
|
|
||||||
/// Seed for deposit authority seed
|
/// Seed for deposit authority seed
|
||||||
const AUTHORITY_DEPOSIT: &[u8] = b"deposit";
|
const AUTHORITY_DEPOSIT: &[u8] = b"deposit";
|
||||||
|
@ -22,6 +25,30 @@ const AUTHORITY_DEPOSIT: &[u8] = b"deposit";
|
||||||
/// Seed for withdraw authority seed
|
/// Seed for withdraw authority seed
|
||||||
const AUTHORITY_WITHDRAW: &[u8] = b"withdraw";
|
const AUTHORITY_WITHDRAW: &[u8] = b"withdraw";
|
||||||
|
|
||||||
|
/// Seed for transient stake account
|
||||||
|
const TRANSIENT_STAKE_SEED: &[u8] = b"transient";
|
||||||
|
|
||||||
|
/// Minimum amount of staked SOL required in a validator stake account to allow
|
||||||
|
/// for merges without a mismatch on credits observed
|
||||||
|
pub const MINIMUM_ACTIVE_STAKE: u64 = LAMPORTS_PER_SOL;
|
||||||
|
|
||||||
|
/// Maximum amount of validator stake accounts to update per
|
||||||
|
/// `UpdateValidatorListBalance` instruction, based on compute limits
|
||||||
|
pub const MAX_VALIDATORS_TO_UPDATE: usize = 10;
|
||||||
|
|
||||||
|
/// Get the stake amount under consideration when calculating pool token
|
||||||
|
/// conversions
|
||||||
|
pub fn minimum_stake_lamports(meta: &Meta) -> u64 {
|
||||||
|
meta.rent_exempt_reserve
|
||||||
|
.saturating_add(MINIMUM_ACTIVE_STAKE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the stake amount under consideration when calculating pool token
|
||||||
|
/// conversions
|
||||||
|
pub fn minimum_reserve_lamports(meta: &Meta) -> u64 {
|
||||||
|
meta.rent_exempt_reserve.saturating_add(1)
|
||||||
|
}
|
||||||
|
|
||||||
/// Generates the deposit authority program address for the stake pool
|
/// Generates the deposit authority program address for the stake pool
|
||||||
pub fn find_deposit_authority_program_address(
|
pub fn find_deposit_authority_program_address(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
|
@ -59,4 +86,20 @@ pub fn find_stake_program_address(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates the stake program address for a validator's vote account
|
||||||
|
pub fn find_transient_stake_program_address(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
vote_account_address: &Pubkey,
|
||||||
|
stake_pool_address: &Pubkey,
|
||||||
|
) -> (Pubkey, u8) {
|
||||||
|
Pubkey::find_program_address(
|
||||||
|
&[
|
||||||
|
TRANSIENT_STAKE_SEED,
|
||||||
|
&vote_account_address.to_bytes()[..32],
|
||||||
|
&stake_pool_address.to_bytes()[..32],
|
||||||
|
],
|
||||||
|
program_id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
solana_program::declare_id!("poo1B9L9nR3CrcaziKVYVpRX6A9Y1LAXYasjjfCbApj");
|
solana_program::declare_id!("poo1B9L9nR3CrcaziKVYVpRX6A9Y1LAXYasjjfCbApj");
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,14 +1,22 @@
|
||||||
//! FIXME copied from the solana stake program
|
//! FIXME copied from the solana stake program
|
||||||
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use {
|
||||||
use solana_program::{
|
borsh::{
|
||||||
clock::{Epoch, UnixTimestamp},
|
maybestd::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult},
|
||||||
instruction::{AccountMeta, Instruction},
|
BorshDeserialize, BorshSchema, BorshSerialize,
|
||||||
pubkey::Pubkey,
|
},
|
||||||
stake_history::StakeHistory,
|
serde_derive::{Deserialize, Serialize},
|
||||||
system_instruction, sysvar,
|
solana_program::{
|
||||||
|
clock::{Epoch, UnixTimestamp},
|
||||||
|
instruction::{AccountMeta, Instruction},
|
||||||
|
msg,
|
||||||
|
program_error::ProgramError,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
stake_history::StakeHistory,
|
||||||
|
system_instruction, sysvar,
|
||||||
|
},
|
||||||
|
std::str::FromStr,
|
||||||
};
|
};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
solana_program::declare_id!("Stake11111111111111111111111111111111111111");
|
solana_program::declare_id!("Stake11111111111111111111111111111111111111");
|
||||||
|
|
||||||
|
@ -125,8 +133,39 @@ pub enum StakeState {
|
||||||
RewardsPool,
|
RewardsPool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BorshDeserialize for StakeState {
|
||||||
|
fn deserialize(buf: &mut &[u8]) -> IoResult<Self> {
|
||||||
|
let u: u32 = BorshDeserialize::deserialize(buf)?;
|
||||||
|
match u {
|
||||||
|
0 => Ok(StakeState::Uninitialized),
|
||||||
|
1 => {
|
||||||
|
let meta: Meta = BorshDeserialize::deserialize(buf)?;
|
||||||
|
Ok(StakeState::Initialized(meta))
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
let meta: Meta = BorshDeserialize::deserialize(buf)?;
|
||||||
|
let stake: Stake = BorshDeserialize::deserialize(buf)?;
|
||||||
|
Ok(StakeState::Stake(meta, stake))
|
||||||
|
}
|
||||||
|
3 => Ok(StakeState::RewardsPool),
|
||||||
|
_ => Err(IoError::new(IoErrorKind::InvalidData, "Invalid enum value")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// FIXME copied from the stake program
|
/// FIXME copied from the stake program
|
||||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
#[derive(
|
||||||
|
BorshSerialize,
|
||||||
|
BorshDeserialize,
|
||||||
|
BorshSchema,
|
||||||
|
Default,
|
||||||
|
Debug,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
PartialEq,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
)]
|
||||||
pub struct Meta {
|
pub struct Meta {
|
||||||
/// FIXME copied from the stake program
|
/// FIXME copied from the stake program
|
||||||
pub rent_exempt_reserve: u64,
|
pub rent_exempt_reserve: u64,
|
||||||
|
@ -137,7 +176,18 @@ pub struct Meta {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FIXME copied from the stake program
|
/// FIXME copied from the stake program
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
#[derive(
|
||||||
|
BorshSerialize,
|
||||||
|
BorshDeserialize,
|
||||||
|
BorshSchema,
|
||||||
|
Debug,
|
||||||
|
Default,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
PartialEq,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
)]
|
||||||
pub struct Stake {
|
pub struct Stake {
|
||||||
/// FIXME copied from the stake program
|
/// FIXME copied from the stake program
|
||||||
pub delegation: Delegation,
|
pub delegation: Delegation,
|
||||||
|
@ -146,7 +196,18 @@ pub struct Stake {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FIXME copied from the stake program
|
/// FIXME copied from the stake program
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
#[derive(
|
||||||
|
BorshSerialize,
|
||||||
|
BorshDeserialize,
|
||||||
|
BorshSchema,
|
||||||
|
Debug,
|
||||||
|
Default,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
PartialEq,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
)]
|
||||||
pub struct Delegation {
|
pub struct Delegation {
|
||||||
/// to whom the stake is delegated
|
/// to whom the stake is delegated
|
||||||
pub voter_pubkey: Pubkey,
|
pub voter_pubkey: Pubkey,
|
||||||
|
@ -161,7 +222,17 @@ pub struct Delegation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FIXME copied from the stake program
|
/// FIXME copied from the stake program
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
#[derive(
|
||||||
|
BorshSerialize,
|
||||||
|
BorshDeserialize,
|
||||||
|
BorshSchema,
|
||||||
|
Debug,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
PartialEq,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
)]
|
||||||
pub enum StakeAuthorize {
|
pub enum StakeAuthorize {
|
||||||
/// FIXME copied from the stake program
|
/// FIXME copied from the stake program
|
||||||
Staker,
|
Staker,
|
||||||
|
@ -169,7 +240,18 @@ pub enum StakeAuthorize {
|
||||||
Withdrawer,
|
Withdrawer,
|
||||||
}
|
}
|
||||||
/// FIXME copied from the stake program
|
/// FIXME copied from the stake program
|
||||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
#[derive(
|
||||||
|
BorshSerialize,
|
||||||
|
BorshDeserialize,
|
||||||
|
BorshSchema,
|
||||||
|
Default,
|
||||||
|
Debug,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
PartialEq,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
)]
|
||||||
pub struct Authorized {
|
pub struct Authorized {
|
||||||
/// FIXME copied from the stake program
|
/// FIXME copied from the stake program
|
||||||
pub staker: Pubkey,
|
pub staker: Pubkey,
|
||||||
|
@ -178,7 +260,18 @@ pub struct Authorized {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FIXME copied from the stake program
|
/// FIXME copied from the stake program
|
||||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone, Copy)]
|
#[derive(
|
||||||
|
BorshSerialize,
|
||||||
|
BorshDeserialize,
|
||||||
|
BorshSchema,
|
||||||
|
Default,
|
||||||
|
Debug,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
PartialEq,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
)]
|
||||||
pub struct Lockup {
|
pub struct Lockup {
|
||||||
/// UnixTimestamp at which this stake will allow withdrawal, unless the
|
/// UnixTimestamp at which this stake will allow withdrawal, unless the
|
||||||
/// transaction is signed by the custodian
|
/// transaction is signed by the custodian
|
||||||
|
@ -200,6 +293,14 @@ impl StakeState {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Get meta
|
||||||
|
pub fn meta(&self) -> Option<&Meta> {
|
||||||
|
match self {
|
||||||
|
StakeState::Initialized(meta) => Some(meta),
|
||||||
|
StakeState::Stake(meta, _) => Some(meta),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// FIXME copied from the stake program
|
/// FIXME copied from the stake program
|
||||||
|
@ -397,6 +498,41 @@ impl Delegation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// FIXME copied from stake program
|
||||||
|
/// Checks if two active delegations are mergeable, required since we cannot recover
|
||||||
|
/// from a CPI error.
|
||||||
|
pub fn active_delegations_can_merge(
|
||||||
|
stake: &Delegation,
|
||||||
|
source: &Delegation,
|
||||||
|
) -> Result<(), ProgramError> {
|
||||||
|
if stake.voter_pubkey != source.voter_pubkey {
|
||||||
|
msg!("Unable to merge due to voter mismatch");
|
||||||
|
Err(ProgramError::InvalidAccountData)
|
||||||
|
} else if (stake.warmup_cooldown_rate - source.warmup_cooldown_rate).abs() < f64::EPSILON
|
||||||
|
&& stake.deactivation_epoch == Epoch::MAX
|
||||||
|
&& source.deactivation_epoch == Epoch::MAX
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
msg!("Unable to merge due to stake deactivation");
|
||||||
|
Err(ProgramError::InvalidAccountData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME copied from stake program
|
||||||
|
/// Checks if two active stakes are mergeable, required since we cannot recover
|
||||||
|
/// from a CPI error.
|
||||||
|
pub fn active_stakes_can_merge(stake: &Stake, source: &Stake) -> Result<(), ProgramError> {
|
||||||
|
active_delegations_can_merge(&stake.delegation, &source.delegation)?;
|
||||||
|
|
||||||
|
if stake.credits_observed == source.credits_observed {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
msg!("Unable to merge due to credits observed mismatch");
|
||||||
|
Err(ProgramError::InvalidAccountData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// FIXME copied from the stake program
|
/// FIXME copied from the stake program
|
||||||
pub fn split_only(
|
pub fn split_only(
|
||||||
stake_pubkey: &Pubkey,
|
stake_pubkey: &Pubkey,
|
||||||
|
@ -508,3 +644,64 @@ pub fn deactivate_stake(stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> In
|
||||||
];
|
];
|
||||||
Instruction::new_with_bincode(id(), &StakeInstruction::Deactivate, account_metas)
|
Instruction::new_with_bincode(id(), &StakeInstruction::Deactivate, account_metas)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use {super::*, crate::borsh::try_from_slice_unchecked, bincode::serialize};
|
||||||
|
|
||||||
|
fn check_borsh_deserialization(stake: StakeState) {
|
||||||
|
let serialized = serialize(&stake).unwrap();
|
||||||
|
let deserialized = StakeState::try_from_slice(&serialized).unwrap();
|
||||||
|
assert_eq!(stake, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bincode_vs_borsh() {
|
||||||
|
check_borsh_deserialization(StakeState::Uninitialized);
|
||||||
|
check_borsh_deserialization(StakeState::RewardsPool);
|
||||||
|
check_borsh_deserialization(StakeState::Initialized(Meta {
|
||||||
|
rent_exempt_reserve: u64::MAX,
|
||||||
|
authorized: Authorized {
|
||||||
|
staker: Pubkey::new_unique(),
|
||||||
|
withdrawer: Pubkey::new_unique(),
|
||||||
|
},
|
||||||
|
lockup: Lockup::default(),
|
||||||
|
}));
|
||||||
|
check_borsh_deserialization(StakeState::Stake(
|
||||||
|
Meta {
|
||||||
|
rent_exempt_reserve: 1,
|
||||||
|
authorized: Authorized {
|
||||||
|
staker: Pubkey::new_unique(),
|
||||||
|
withdrawer: Pubkey::new_unique(),
|
||||||
|
},
|
||||||
|
lockup: Lockup::default(),
|
||||||
|
},
|
||||||
|
Stake {
|
||||||
|
delegation: Delegation {
|
||||||
|
voter_pubkey: Pubkey::new_unique(),
|
||||||
|
stake: u64::MAX,
|
||||||
|
activation_epoch: Epoch::MAX,
|
||||||
|
deactivation_epoch: Epoch::MAX,
|
||||||
|
warmup_cooldown_rate: f64::MAX,
|
||||||
|
},
|
||||||
|
credits_observed: 1,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn borsh_deserialization_live_data() {
|
||||||
|
let data = [
|
||||||
|
1, 0, 0, 0, 128, 213, 34, 0, 0, 0, 0, 0, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35,
|
||||||
|
119, 124, 168, 12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149,
|
||||||
|
224, 109, 52, 100, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35, 119, 124, 168, 12, 120,
|
||||||
|
216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149, 224, 109, 52, 100, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0,
|
||||||
|
];
|
||||||
|
let _deserialized = try_from_slice_unchecked::<StakeState>(&data).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
//! State transition types
|
//! State transition types
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{error::StakePoolError, instruction::Fee},
|
crate::error::StakePoolError,
|
||||||
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
|
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
|
||||||
solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey},
|
solana_program::{account_info::AccountInfo, msg, program_error::ProgramError, pubkey::Pubkey},
|
||||||
spl_math::checked_ceil_div::CheckedCeilDiv,
|
spl_math::checked_ceil_div::CheckedCeilDiv,
|
||||||
std::convert::TryFrom,
|
std::convert::TryFrom,
|
||||||
};
|
};
|
||||||
|
@ -31,66 +31,94 @@ impl Default for AccountType {
|
||||||
pub struct StakePool {
|
pub struct StakePool {
|
||||||
/// Account type, must be StakePool currently
|
/// Account type, must be StakePool currently
|
||||||
pub account_type: AccountType,
|
pub account_type: AccountType,
|
||||||
/// Owner authority
|
|
||||||
/// allows for updating the staking authority
|
/// Manager authority, allows for updating the staker, manager, and fee account
|
||||||
pub owner: Pubkey,
|
pub manager: Pubkey,
|
||||||
/// Deposit authority bump seed
|
|
||||||
/// for `create_program_address(&[state::StakePool account, "deposit"])`
|
/// Staker authority, allows for adding and removing validators, and managing stake
|
||||||
pub deposit_bump_seed: u8,
|
/// distribution
|
||||||
|
pub staker: Pubkey,
|
||||||
|
|
||||||
|
/// Deposit authority
|
||||||
|
///
|
||||||
|
/// If a depositor pubkey is specified on initialization, then deposits must be
|
||||||
|
/// signed by this authority. If no deposit authority is specified,
|
||||||
|
/// then the stake pool will default to the result of:
|
||||||
|
/// `Pubkey::find_program_address(
|
||||||
|
/// &[&stake_pool_address.to_bytes()[..32], b"deposit"],
|
||||||
|
/// program_id,
|
||||||
|
/// )`
|
||||||
|
pub deposit_authority: Pubkey,
|
||||||
|
|
||||||
/// Withdrawal authority bump seed
|
/// Withdrawal authority bump seed
|
||||||
/// for `create_program_address(&[state::StakePool account, "withdrawal"])`
|
/// for `create_program_address(&[state::StakePool account, "withdrawal"])`
|
||||||
pub withdraw_bump_seed: u8,
|
pub withdraw_bump_seed: u8,
|
||||||
|
|
||||||
/// Validator stake list storage account
|
/// Validator stake list storage account
|
||||||
pub validator_list: Pubkey,
|
pub validator_list: Pubkey,
|
||||||
|
|
||||||
|
/// Reserve stake account, holds deactivated stake
|
||||||
|
pub reserve_stake: Pubkey,
|
||||||
|
|
||||||
/// Pool Mint
|
/// Pool Mint
|
||||||
pub pool_mint: Pubkey,
|
pub pool_mint: Pubkey,
|
||||||
/// Owner fee account
|
|
||||||
pub owner_fee_account: Pubkey,
|
/// Manager fee account
|
||||||
|
pub manager_fee_account: Pubkey,
|
||||||
|
|
||||||
/// Pool token program id
|
/// Pool token program id
|
||||||
pub token_program_id: Pubkey,
|
pub token_program_id: Pubkey,
|
||||||
/// total stake under management
|
|
||||||
pub stake_total: u64,
|
/// Total stake under management.
|
||||||
/// total pool
|
/// Note that if `last_update_epoch` does not match the current epoch then
|
||||||
pub pool_total: u64,
|
/// this field may not be accurate
|
||||||
/// Last epoch stake_total field was updated
|
pub total_stake_lamports: u64,
|
||||||
|
|
||||||
|
/// Total supply of pool tokens (should always match the supply in the Pool Mint)
|
||||||
|
pub pool_token_supply: u64,
|
||||||
|
|
||||||
|
/// Last epoch the `total_stake_lamports` field was updated
|
||||||
pub last_update_epoch: u64,
|
pub last_update_epoch: u64,
|
||||||
|
|
||||||
/// Fee applied to deposits
|
/// Fee applied to deposits
|
||||||
pub fee: Fee,
|
pub fee: Fee,
|
||||||
}
|
}
|
||||||
impl StakePool {
|
impl StakePool {
|
||||||
/// calculate the pool tokens that should be minted
|
/// calculate the pool tokens that should be minted for a deposit of `stake_lamports`
|
||||||
pub fn calc_pool_deposit_amount(&self, stake_lamports: u64) -> Option<u64> {
|
pub fn calc_pool_tokens_for_deposit(&self, stake_lamports: u64) -> Option<u64> {
|
||||||
if self.stake_total == 0 {
|
if self.total_stake_lamports == 0 || self.pool_token_supply == 0 {
|
||||||
return Some(stake_lamports);
|
return Some(stake_lamports);
|
||||||
}
|
}
|
||||||
u64::try_from(
|
u64::try_from(
|
||||||
(stake_lamports as u128)
|
(stake_lamports as u128)
|
||||||
.checked_mul(self.pool_total as u128)?
|
.checked_mul(self.pool_token_supply as u128)?
|
||||||
.checked_div(self.stake_total as u128)?,
|
.checked_div(self.total_stake_lamports as u128)?,
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
/// calculate the pool tokens that should be withdrawn
|
/// calculate the pool tokens that should be burned for a withdrawal of `stake_lamports`
|
||||||
pub fn calc_pool_withdraw_amount(&self, stake_lamports: u64) -> Option<u64> {
|
pub fn calc_pool_tokens_for_withdraw(&self, stake_lamports: u64) -> Option<u64> {
|
||||||
let (quotient, _) = (stake_lamports as u128)
|
let (quotient, _) = (stake_lamports as u128)
|
||||||
.checked_mul(self.pool_total as u128)?
|
.checked_mul(self.pool_token_supply as u128)?
|
||||||
.checked_ceil_div(self.stake_total as u128)?;
|
.checked_ceil_div(self.total_stake_lamports as u128)?;
|
||||||
u64::try_from(quotient).ok()
|
u64::try_from(quotient).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// calculate lamports amount on withdrawal
|
/// calculate lamports amount on withdrawal
|
||||||
pub fn calc_lamports_withdraw_amount(&self, pool_tokens: u64) -> Option<u64> {
|
pub fn calc_lamports_withdraw_amount(&self, pool_tokens: u64) -> Option<u64> {
|
||||||
u64::try_from(
|
u64::try_from(
|
||||||
(pool_tokens as u128)
|
(pool_tokens as u128)
|
||||||
.checked_mul(self.stake_total as u128)?
|
.checked_mul(self.total_stake_lamports as u128)?
|
||||||
.checked_div(self.pool_total as u128)?,
|
.checked_div(self.pool_token_supply as u128)?,
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
/// calculate the fee in pool tokens that goes to the owner
|
/// calculate the fee in pool tokens that goes to the manager
|
||||||
pub fn calc_fee_amount(&self, pool_amount: u64) -> Option<u64> {
|
pub fn calc_fee_amount(&self, reward_lamports: u64) -> Option<u64> {
|
||||||
if self.fee.denominator == 0 {
|
if self.fee.denominator == 0 {
|
||||||
return Some(0);
|
return Some(0);
|
||||||
}
|
}
|
||||||
|
let pool_amount = self.calc_pool_tokens_for_deposit(reward_lamports)?;
|
||||||
u64::try_from(
|
u64::try_from(
|
||||||
(pool_amount as u128)
|
(pool_amount as u128)
|
||||||
.checked_mul(self.fee.numerator as u128)?
|
.checked_mul(self.fee.numerator as u128)?
|
||||||
|
@ -107,18 +135,23 @@ impl StakePool {
|
||||||
authority_seed: &[u8],
|
authority_seed: &[u8],
|
||||||
bump_seed: u8,
|
bump_seed: u8,
|
||||||
) -> Result<(), ProgramError> {
|
) -> Result<(), ProgramError> {
|
||||||
if *authority_address
|
let expected_address = Pubkey::create_program_address(
|
||||||
== Pubkey::create_program_address(
|
&[
|
||||||
&[
|
&stake_pool_address.to_bytes()[..32],
|
||||||
&stake_pool_address.to_bytes()[..32],
|
authority_seed,
|
||||||
authority_seed,
|
&[bump_seed],
|
||||||
&[bump_seed],
|
],
|
||||||
],
|
program_id,
|
||||||
program_id,
|
)?;
|
||||||
)?
|
|
||||||
{
|
if *authority_address == expected_address {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
msg!(
|
||||||
|
"Incorrect authority provided, expected {}, received {}",
|
||||||
|
expected_address,
|
||||||
|
authority_address
|
||||||
|
);
|
||||||
Err(StakePoolError::InvalidProgramAddress.into())
|
Err(StakePoolError::InvalidProgramAddress.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,32 +172,94 @@ impl StakePool {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/// Checks that the deposit authority is valid
|
/// Checks that the deposit authority is valid
|
||||||
pub(crate) fn check_authority_deposit(
|
pub(crate) fn check_deposit_authority(
|
||||||
&self,
|
&self,
|
||||||
deposit_authority: &Pubkey,
|
deposit_authority: &Pubkey,
|
||||||
program_id: &Pubkey,
|
|
||||||
stake_pool_address: &Pubkey,
|
|
||||||
) -> Result<(), ProgramError> {
|
) -> Result<(), ProgramError> {
|
||||||
Self::check_authority(
|
if self.deposit_authority == *deposit_authority {
|
||||||
deposit_authority,
|
Ok(())
|
||||||
program_id,
|
} else {
|
||||||
stake_pool_address,
|
Err(StakePoolError::InvalidProgramAddress.into())
|
||||||
crate::AUTHORITY_DEPOSIT,
|
}
|
||||||
self.deposit_bump_seed,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check owner validity and signature
|
/// Check staker validity and signature
|
||||||
pub(crate) fn check_owner(&self, owner_info: &AccountInfo) -> Result<(), ProgramError> {
|
pub(crate) fn check_mint(&self, mint_info: &AccountInfo) -> Result<(), ProgramError> {
|
||||||
if *owner_info.key != self.owner {
|
if *mint_info.key != self.pool_mint {
|
||||||
return Err(StakePoolError::WrongOwner.into());
|
Err(StakePoolError::WrongPoolMint.into())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
if !owner_info.is_signer {
|
}
|
||||||
|
|
||||||
|
/// Check manager validity and signature
|
||||||
|
pub(crate) fn check_manager(&self, manager_info: &AccountInfo) -> Result<(), ProgramError> {
|
||||||
|
if *manager_info.key != self.manager {
|
||||||
|
msg!(
|
||||||
|
"Incorrect manager provided, expected {}, received {}",
|
||||||
|
self.manager,
|
||||||
|
manager_info.key
|
||||||
|
);
|
||||||
|
return Err(StakePoolError::WrongManager.into());
|
||||||
|
}
|
||||||
|
if !manager_info.is_signer {
|
||||||
|
msg!("Manager signature missing");
|
||||||
return Err(StakePoolError::SignatureMissing.into());
|
return Err(StakePoolError::SignatureMissing.into());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check staker validity and signature
|
||||||
|
pub(crate) fn check_staker(&self, staker_info: &AccountInfo) -> Result<(), ProgramError> {
|
||||||
|
if *staker_info.key != self.staker {
|
||||||
|
msg!(
|
||||||
|
"Incorrect staker provided, expected {}, received {}",
|
||||||
|
self.staker,
|
||||||
|
staker_info.key
|
||||||
|
);
|
||||||
|
return Err(StakePoolError::WrongStaker.into());
|
||||||
|
}
|
||||||
|
if !staker_info.is_signer {
|
||||||
|
msg!("Staker signature missing");
|
||||||
|
return Err(StakePoolError::SignatureMissing.into());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check the validator list is valid
|
||||||
|
pub fn check_validator_list(
|
||||||
|
&self,
|
||||||
|
validator_list_info: &AccountInfo,
|
||||||
|
) -> Result<(), ProgramError> {
|
||||||
|
if *validator_list_info.key != self.validator_list {
|
||||||
|
msg!(
|
||||||
|
"Invalid validator list provided, expected {}, received {}",
|
||||||
|
self.validator_list,
|
||||||
|
validator_list_info.key
|
||||||
|
);
|
||||||
|
Err(StakePoolError::InvalidValidatorStakeList.into())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check the validator list is valid
|
||||||
|
pub fn check_reserve_stake(
|
||||||
|
&self,
|
||||||
|
reserve_stake_info: &AccountInfo,
|
||||||
|
) -> Result<(), ProgramError> {
|
||||||
|
if *reserve_stake_info.key != self.reserve_stake {
|
||||||
|
msg!(
|
||||||
|
"Invalid reserve stake provided, expected {}, received {}",
|
||||||
|
self.reserve_stake,
|
||||||
|
reserve_stake_info.key
|
||||||
|
);
|
||||||
|
Err(StakePoolError::InvalidProgramAddress.into())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if StakePool is actually initialized as a stake pool
|
/// Check if StakePool is actually initialized as a stake pool
|
||||||
pub fn is_valid(&self) -> bool {
|
pub fn is_valid(&self) -> bool {
|
||||||
self.account_type == AccountType::StakePool
|
self.account_type == AccountType::StakePool
|
||||||
|
@ -186,21 +281,45 @@ pub struct ValidatorList {
|
||||||
/// Maximum allowable number of validators
|
/// Maximum allowable number of validators
|
||||||
pub max_validators: u32,
|
pub max_validators: u32,
|
||||||
|
|
||||||
/// List of all validator stake accounts and their info
|
/// List of stake info for each validator in the pool
|
||||||
pub validators: Vec<ValidatorStakeInfo>,
|
pub validators: Vec<ValidatorStakeInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Status of the stake account in the validator list, for accounting
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||||
|
pub enum StakeStatus {
|
||||||
|
/// Stake account is active, there may be a transient stake as well
|
||||||
|
Active,
|
||||||
|
/// Only transient stake account exists, when a transient stake is
|
||||||
|
/// deactivating during validator removal
|
||||||
|
DeactivatingTransient,
|
||||||
|
/// No more validator stake accounts exist, entry ready for removal during
|
||||||
|
/// `UpdateStakePoolBalance`
|
||||||
|
ReadyForRemoval,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for StakeStatus {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Active
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Information about the singe validator stake account
|
/// Information about the singe validator stake account
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||||
pub struct ValidatorStakeInfo {
|
pub struct ValidatorStakeInfo {
|
||||||
|
/// Status of the validator stake account
|
||||||
|
pub status: StakeStatus,
|
||||||
|
|
||||||
/// Validator vote account address
|
/// Validator vote account address
|
||||||
pub vote_account: Pubkey,
|
pub vote_account_address: Pubkey,
|
||||||
|
|
||||||
/// Balance of the validator's stake account
|
/// Amount of stake delegated to this validator
|
||||||
pub balance: u64,
|
/// Note that if `last_update_epoch` does not match the current epoch then this field may not
|
||||||
|
/// be accurate
|
||||||
|
pub stake_lamports: u64,
|
||||||
|
|
||||||
/// Last epoch balance field was updated
|
/// Last epoch the `stake_lamports` field was updated
|
||||||
pub last_update_epoch: u64,
|
pub last_update_epoch: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,27 +336,27 @@ impl ValidatorList {
|
||||||
/// Calculate the number of validator entries that fit in the provided length
|
/// Calculate the number of validator entries that fit in the provided length
|
||||||
pub fn calculate_max_validators(buffer_length: usize) -> usize {
|
pub fn calculate_max_validators(buffer_length: usize) -> usize {
|
||||||
let header_size = 1 + 4 + 4;
|
let header_size = 1 + 4 + 4;
|
||||||
buffer_length.saturating_sub(header_size) / 48
|
buffer_length.saturating_sub(header_size) / 49
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if contains validator with particular pubkey
|
/// Check if contains validator with particular pubkey
|
||||||
pub fn contains(&self, vote_account: &Pubkey) -> bool {
|
pub fn contains(&self, vote_account_address: &Pubkey) -> bool {
|
||||||
self.validators
|
self.validators
|
||||||
.iter()
|
.iter()
|
||||||
.any(|x| x.vote_account == *vote_account)
|
.any(|x| x.vote_account_address == *vote_account_address)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if contains validator with particular pubkey
|
/// Check if contains validator with particular pubkey
|
||||||
pub fn find_mut(&mut self, vote_account: &Pubkey) -> Option<&mut ValidatorStakeInfo> {
|
pub fn find_mut(&mut self, vote_account_address: &Pubkey) -> Option<&mut ValidatorStakeInfo> {
|
||||||
self.validators
|
self.validators
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.find(|x| x.vote_account == *vote_account)
|
.find(|x| x.vote_account_address == *vote_account_address)
|
||||||
}
|
}
|
||||||
/// Check if contains validator with particular pubkey
|
/// Check if contains validator with particular pubkey
|
||||||
pub fn find(&self, vote_account: &Pubkey) -> Option<&ValidatorStakeInfo> {
|
pub fn find(&self, vote_account_address: &Pubkey) -> Option<&ValidatorStakeInfo> {
|
||||||
self.validators
|
self.validators
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.vote_account == *vote_account)
|
.find(|x| x.vote_account_address == *vote_account_address)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if validator stake list is actually initialized as a validator stake list
|
/// Check if validator stake list is actually initialized as a validator stake list
|
||||||
|
@ -251,6 +370,17 @@ impl ValidatorList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fee rate as a ratio, minted on `UpdateStakePoolBalance` as a proportion of
|
||||||
|
/// the rewards
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)]
|
||||||
|
pub struct Fee {
|
||||||
|
/// denominator of the fee ratio
|
||||||
|
pub denominator: u64,
|
||||||
|
/// numerator of the fee ratio
|
||||||
|
pub numerator: u64,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use {
|
use {
|
||||||
|
@ -294,18 +424,21 @@ mod test {
|
||||||
max_validators,
|
max_validators,
|
||||||
validators: vec![
|
validators: vec![
|
||||||
ValidatorStakeInfo {
|
ValidatorStakeInfo {
|
||||||
vote_account: Pubkey::new_from_array([1; 32]),
|
status: StakeStatus::Active,
|
||||||
balance: 123456789,
|
vote_account_address: Pubkey::new_from_array([1; 32]),
|
||||||
|
stake_lamports: 123456789,
|
||||||
last_update_epoch: 987654321,
|
last_update_epoch: 987654321,
|
||||||
},
|
},
|
||||||
ValidatorStakeInfo {
|
ValidatorStakeInfo {
|
||||||
vote_account: Pubkey::new_from_array([2; 32]),
|
status: StakeStatus::DeactivatingTransient,
|
||||||
balance: 998877665544,
|
vote_account_address: Pubkey::new_from_array([2; 32]),
|
||||||
|
stake_lamports: 998877665544,
|
||||||
last_update_epoch: 11223445566,
|
last_update_epoch: 11223445566,
|
||||||
},
|
},
|
||||||
ValidatorStakeInfo {
|
ValidatorStakeInfo {
|
||||||
vote_account: Pubkey::new_from_array([3; 32]),
|
status: StakeStatus::ReadyForRemoval,
|
||||||
balance: 0,
|
vote_account_address: Pubkey::new_from_array([3; 32]),
|
||||||
|
stake_lamports: 0,
|
||||||
last_update_epoch: 999999999999999,
|
last_update_epoch: 999999999999999,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,386 @@
|
||||||
|
#![cfg(feature = "test-bpf")]
|
||||||
|
|
||||||
|
mod helpers;
|
||||||
|
|
||||||
|
use {
|
||||||
|
bincode::deserialize,
|
||||||
|
helpers::*,
|
||||||
|
solana_program::{
|
||||||
|
clock::Epoch, hash::Hash, instruction::InstructionError, pubkey::Pubkey,
|
||||||
|
system_instruction::SystemError,
|
||||||
|
},
|
||||||
|
solana_program_test::*,
|
||||||
|
solana_sdk::{
|
||||||
|
signature::{Keypair, Signer},
|
||||||
|
transaction::{Transaction, TransactionError},
|
||||||
|
},
|
||||||
|
spl_stake_pool::{error::StakePoolError, id, instruction, stake_program},
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn setup() -> (
|
||||||
|
BanksClient,
|
||||||
|
Keypair,
|
||||||
|
Hash,
|
||||||
|
StakePoolAccounts,
|
||||||
|
ValidatorStakeAccount,
|
||||||
|
DepositStakeAccount,
|
||||||
|
u64,
|
||||||
|
) {
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
stake_pool_accounts
|
||||||
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let validator_stake_account = simple_add_validator_to_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let deposit_info = simple_deposit(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
&validator_stake_account,
|
||||||
|
100_000_000,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let lamports = deposit_info.stake_lamports / 2;
|
||||||
|
|
||||||
|
(
|
||||||
|
banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
validator_stake_account,
|
||||||
|
deposit_info,
|
||||||
|
lamports,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn success() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
validator_stake,
|
||||||
|
_deposit_info,
|
||||||
|
decrease_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
// Save validator stake
|
||||||
|
let pre_validator_stake_account =
|
||||||
|
get_account(&mut banks_client, &validator_stake.stake_account).await;
|
||||||
|
|
||||||
|
// Check no transient stake
|
||||||
|
let transient_account = banks_client
|
||||||
|
.get_account(validator_stake.transient_stake_account)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(transient_account.is_none());
|
||||||
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.decrease_validator_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
decrease_lamports,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
// Check validator stake account balance
|
||||||
|
let validator_stake_account =
|
||||||
|
get_account(&mut banks_client, &validator_stake.stake_account).await;
|
||||||
|
let validator_stake_state =
|
||||||
|
deserialize::<stake_program::StakeState>(&validator_stake_account.data).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
pre_validator_stake_account.lamports - decrease_lamports,
|
||||||
|
validator_stake_account.lamports
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
validator_stake_state
|
||||||
|
.delegation()
|
||||||
|
.unwrap()
|
||||||
|
.deactivation_epoch,
|
||||||
|
Epoch::MAX
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check transient stake account state and balance
|
||||||
|
let transient_stake_account =
|
||||||
|
get_account(&mut banks_client, &validator_stake.transient_stake_account).await;
|
||||||
|
let transient_stake_state =
|
||||||
|
deserialize::<stake_program::StakeState>(&transient_stake_account.data).unwrap();
|
||||||
|
assert_eq!(transient_stake_account.lamports, decrease_lamports);
|
||||||
|
assert_ne!(
|
||||||
|
transient_stake_state
|
||||||
|
.delegation()
|
||||||
|
.unwrap()
|
||||||
|
.deactivation_epoch,
|
||||||
|
Epoch::MAX
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_wrong_withdraw_authority() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
validator_stake,
|
||||||
|
_deposit_info,
|
||||||
|
decrease_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
let wrong_authority = Pubkey::new_unique();
|
||||||
|
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&[instruction::decrease_validator_stake(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&wrong_authority,
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
decrease_lamports,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer, &stake_pool_accounts.staker],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
let error = banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
|
||||||
|
let program_error = StakePoolError::InvalidProgramAddress as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error occurs while decreasing with wrong withdraw authority"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_wrong_validator_list() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
mut stake_pool_accounts,
|
||||||
|
validator_stake,
|
||||||
|
_deposit_info,
|
||||||
|
decrease_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
stake_pool_accounts.validator_list = Keypair::new();
|
||||||
|
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&[instruction::decrease_validator_stake(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&stake_pool_accounts.withdraw_authority,
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
decrease_lamports,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer, &stake_pool_accounts.staker],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
let error = banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
|
||||||
|
let program_error = StakePoolError::InvalidValidatorStakeList as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error occurs while decreasing with wrong validator stake list account"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_unknown_validator() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
_validator_stake,
|
||||||
|
_deposit_info,
|
||||||
|
decrease_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
let unknown_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey());
|
||||||
|
unknown_stake
|
||||||
|
.create_and_delegate(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts.staker,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&[instruction::decrease_validator_stake(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&stake_pool_accounts.withdraw_authority,
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
&unknown_stake.stake_account,
|
||||||
|
&unknown_stake.transient_stake_account,
|
||||||
|
decrease_lamports,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer, &stake_pool_accounts.staker],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
let error = banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
|
||||||
|
let program_error = StakePoolError::ValidatorNotFound as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error occurs while decreasing stake from unknown validator"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_decrease_twice() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
validator_stake,
|
||||||
|
_deposit_info,
|
||||||
|
decrease_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.decrease_validator_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
decrease_lamports / 3,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.decrease_validator_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
decrease_lamports / 2,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
|
||||||
|
let program_error = SystemError::AccountAlreadyInUse as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_small_lamport_amount() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
validator_stake,
|
||||||
|
_deposit_info,
|
||||||
|
_decrease_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
let rent = banks_client.get_rent().await.unwrap();
|
||||||
|
let lamports = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
|
||||||
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.decrease_validator_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
lamports,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::InvalidError) => {}
|
||||||
|
_ => panic!("Wrong error occurs while try to decrease small stake"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_overdraw_validator() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
validator_stake,
|
||||||
|
deposit_info,
|
||||||
|
_decrease_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.decrease_validator_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
deposit_info.stake_lamports * 1_000_000,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::InsufficientFunds) => {}
|
||||||
|
_ => panic!("Wrong error occurs while overdrawing stake account on decrease"),
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
|
bincode::deserialize,
|
||||||
borsh::{BorshDeserialize, BorshSerialize},
|
borsh::{BorshDeserialize, BorshSerialize},
|
||||||
helpers::*,
|
helpers::*,
|
||||||
solana_program::{
|
solana_program::{
|
||||||
|
@ -19,7 +20,8 @@ use {
|
||||||
transport::TransportError,
|
transport::TransportError,
|
||||||
},
|
},
|
||||||
spl_stake_pool::{
|
spl_stake_pool::{
|
||||||
borsh::try_from_slice_unchecked, error, id, instruction, stake_program, state,
|
borsh::try_from_slice_unchecked, error, id, instruction, minimum_stake_lamports,
|
||||||
|
stake_program, state,
|
||||||
},
|
},
|
||||||
spl_token::error as token_error,
|
spl_token::error as token_error,
|
||||||
};
|
};
|
||||||
|
@ -34,7 +36,7 @@ async fn setup() -> (
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -78,6 +80,7 @@ async fn test_stake_pool_deposit() {
|
||||||
&user_stake,
|
&user_stake,
|
||||||
&authorized,
|
&authorized,
|
||||||
&lockup,
|
&lockup,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -85,6 +88,7 @@ async fn test_stake_pool_deposit() {
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
|
&validator_stake_account.validator,
|
||||||
&validator_stake_account.vote,
|
&validator_stake_account.vote,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -98,28 +102,6 @@ async fn test_stake_pool_deposit() {
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Change authority to the stake pool's deposit
|
|
||||||
authorize_stake_account(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_stake.pubkey(),
|
|
||||||
&stake_authority,
|
|
||||||
&stake_pool_accounts.deposit_authority,
|
|
||||||
stake_program::StakeAuthorize::Withdrawer,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
authorize_stake_account(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_stake.pubkey(),
|
|
||||||
&stake_authority,
|
|
||||||
&stake_pool_accounts.deposit_authority,
|
|
||||||
stake_program::StakeAuthorize::Staker,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// make pool token account
|
// make pool token account
|
||||||
let user_pool_account = Keypair::new();
|
let user_pool_account = Keypair::new();
|
||||||
create_token_account(
|
create_token_account(
|
||||||
|
@ -159,6 +141,7 @@ async fn test_stake_pool_deposit() {
|
||||||
&user_stake.pubkey(),
|
&user_stake.pubkey(),
|
||||||
&user_pool_account.pubkey(),
|
&user_pool_account.pubkey(),
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
|
&stake_authority,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -171,30 +154,23 @@ async fn test_stake_pool_deposit() {
|
||||||
.is_none());
|
.is_none());
|
||||||
|
|
||||||
let tokens_issued = stake_lamports; // For now tokens are 1:1 to stake
|
let tokens_issued = stake_lamports; // For now tokens are 1:1 to stake
|
||||||
let fee = stake_pool_accounts.calculate_fee(tokens_issued);
|
|
||||||
|
|
||||||
// Stake pool should add its balance to the pool balance
|
// Stake pool should add its balance to the pool balance
|
||||||
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
|
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
|
||||||
let stake_pool = state::StakePool::try_from_slice(&stake_pool.data.as_slice()).unwrap();
|
let stake_pool = state::StakePool::try_from_slice(&stake_pool.data.as_slice()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_pool.stake_total,
|
stake_pool.total_stake_lamports,
|
||||||
stake_pool_before.stake_total + stake_lamports
|
stake_pool_before.total_stake_lamports + stake_lamports
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_pool.pool_total,
|
stake_pool.pool_token_supply,
|
||||||
stake_pool_before.pool_total + tokens_issued
|
stake_pool_before.pool_token_supply + tokens_issued
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check minted tokens
|
// Check minted tokens
|
||||||
let user_token_balance =
|
let user_token_balance =
|
||||||
get_token_balance(&mut banks_client, &user_pool_account.pubkey()).await;
|
get_token_balance(&mut banks_client, &user_pool_account.pubkey()).await;
|
||||||
assert_eq!(user_token_balance, tokens_issued - fee);
|
assert_eq!(user_token_balance, tokens_issued);
|
||||||
let pool_fee_token_balance = get_token_balance(
|
|
||||||
&mut banks_client,
|
|
||||||
&stake_pool_accounts.pool_fee_account.pubkey(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert_eq!(pool_fee_token_balance, fee);
|
|
||||||
|
|
||||||
// Check balances in validator stake account list storage
|
// Check balances in validator stake account list storage
|
||||||
let validator_list = get_account(
|
let validator_list = get_account(
|
||||||
|
@ -208,16 +184,19 @@ async fn test_stake_pool_deposit() {
|
||||||
.find(&validator_stake_account.vote.pubkey())
|
.find(&validator_stake_account.vote.pubkey())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
validator_stake_item.balance,
|
validator_stake_item.stake_lamports,
|
||||||
validator_stake_item_before.balance + stake_lamports
|
validator_stake_item_before.stake_lamports + stake_lamports
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check validator stake account actual SOL balance
|
// Check validator stake account actual SOL balance
|
||||||
let validator_stake_account =
|
let validator_stake_account =
|
||||||
get_account(&mut banks_client, &validator_stake_account.stake_account).await;
|
get_account(&mut banks_client, &validator_stake_account.stake_account).await;
|
||||||
|
let stake_state =
|
||||||
|
deserialize::<stake_program::StakeState>(&validator_stake_account.data).unwrap();
|
||||||
|
let meta = stake_state.meta().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
validator_stake_account.lamports,
|
validator_stake_account.lamports - minimum_stake_lamports(&meta),
|
||||||
validator_stake_item.balance
|
validator_stake_item.stake_lamports
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +232,6 @@ async fn test_stake_pool_deposit_with_wrong_stake_program_id() {
|
||||||
AccountMeta::new(user_stake.pubkey(), false),
|
AccountMeta::new(user_stake.pubkey(), false),
|
||||||
AccountMeta::new(validator_stake_account.stake_account, false),
|
AccountMeta::new(validator_stake_account.stake_account, false),
|
||||||
AccountMeta::new(user_pool_account.pubkey(), false),
|
AccountMeta::new(user_pool_account.pubkey(), false),
|
||||||
AccountMeta::new(stake_pool_accounts.pool_fee_account.pubkey(), false),
|
|
||||||
AccountMeta::new(stake_pool_accounts.pool_mint.pubkey(), false),
|
AccountMeta::new(stake_pool_accounts.pool_mint.pubkey(), false),
|
||||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
|
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
|
||||||
|
@ -284,75 +262,6 @@ async fn test_stake_pool_deposit_with_wrong_stake_program_id() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_stake_pool_deposit_with_wrong_pool_fee_account() {
|
|
||||||
let (
|
|
||||||
mut banks_client,
|
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
mut stake_pool_accounts,
|
|
||||||
validator_stake_account,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let user = Keypair::new();
|
|
||||||
// make stake account
|
|
||||||
let user_stake = Keypair::new();
|
|
||||||
let lockup = stake_program::Lockup::default();
|
|
||||||
let authorized = stake_program::Authorized {
|
|
||||||
staker: stake_pool_accounts.deposit_authority,
|
|
||||||
withdrawer: stake_pool_accounts.deposit_authority,
|
|
||||||
};
|
|
||||||
create_independent_stake_account(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_stake,
|
|
||||||
&authorized,
|
|
||||||
&lockup,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// make pool token account
|
|
||||||
let user_pool_account = Keypair::new();
|
|
||||||
create_token_account(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_pool_account,
|
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
|
||||||
&user.pubkey(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let wrong_pool_fee_acc = Keypair::new();
|
|
||||||
stake_pool_accounts.pool_fee_account = wrong_pool_fee_acc;
|
|
||||||
|
|
||||||
let transaction_error = stake_pool_accounts
|
|
||||||
.deposit_stake(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_stake.pubkey(),
|
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
&validator_stake_account.stake_account,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.err()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
match transaction_error {
|
|
||||||
TransportError::TransactionError(TransactionError::InstructionError(
|
|
||||||
_,
|
|
||||||
InstructionError::Custom(error_index),
|
|
||||||
)) => {
|
|
||||||
let program_error = error::StakePoolError::InvalidFeeAccount as u32;
|
|
||||||
assert_eq!(error_index, program_error);
|
|
||||||
}
|
|
||||||
_ => panic!("Wrong error occurs while try to make a deposit with wrong pool fee account"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_stake_pool_deposit_with_wrong_token_program_id() {
|
async fn test_stake_pool_deposit_with_wrong_token_program_id() {
|
||||||
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake_account) =
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake_account) =
|
||||||
|
@ -363,8 +272,8 @@ async fn test_stake_pool_deposit_with_wrong_token_program_id() {
|
||||||
let user_stake = Keypair::new();
|
let user_stake = Keypair::new();
|
||||||
let lockup = stake_program::Lockup::default();
|
let lockup = stake_program::Lockup::default();
|
||||||
let authorized = stake_program::Authorized {
|
let authorized = stake_program::Authorized {
|
||||||
staker: stake_pool_accounts.deposit_authority,
|
staker: user.pubkey(),
|
||||||
withdrawer: stake_pool_accounts.deposit_authority,
|
withdrawer: user.pubkey(),
|
||||||
};
|
};
|
||||||
create_independent_stake_account(
|
create_independent_stake_account(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
|
@ -373,6 +282,7 @@ async fn test_stake_pool_deposit_with_wrong_token_program_id() {
|
||||||
&user_stake,
|
&user_stake,
|
||||||
&authorized,
|
&authorized,
|
||||||
&lockup,
|
&lockup,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -392,23 +302,21 @@ async fn test_stake_pool_deposit_with_wrong_token_program_id() {
|
||||||
let wrong_token_program = Keypair::new();
|
let wrong_token_program = Keypair::new();
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(
|
let mut transaction = Transaction::new_with_payer(
|
||||||
&[instruction::deposit(
|
&instruction::deposit(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&stake_pool_accounts.validator_list.pubkey(),
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
&stake_pool_accounts.deposit_authority,
|
|
||||||
&stake_pool_accounts.withdraw_authority,
|
&stake_pool_accounts.withdraw_authority,
|
||||||
&user_stake.pubkey(),
|
&user_stake.pubkey(),
|
||||||
|
&user.pubkey(),
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
&user_pool_account.pubkey(),
|
&user_pool_account.pubkey(),
|
||||||
&stake_pool_accounts.pool_fee_account.pubkey(),
|
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
&wrong_token_program.pubkey(),
|
&wrong_token_program.pubkey(),
|
||||||
)
|
),
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
transaction.sign(&[&payer], recent_blockhash);
|
transaction.sign(&[&payer, &user], recent_blockhash);
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
@ -438,8 +346,8 @@ async fn test_stake_pool_deposit_with_wrong_validator_list_account() {
|
||||||
let user_stake = Keypair::new();
|
let user_stake = Keypair::new();
|
||||||
let lockup = stake_program::Lockup::default();
|
let lockup = stake_program::Lockup::default();
|
||||||
let authorized = stake_program::Authorized {
|
let authorized = stake_program::Authorized {
|
||||||
staker: stake_pool_accounts.deposit_authority,
|
staker: user.pubkey(),
|
||||||
withdrawer: stake_pool_accounts.deposit_authority,
|
withdrawer: user.pubkey(),
|
||||||
};
|
};
|
||||||
create_independent_stake_account(
|
create_independent_stake_account(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
|
@ -448,6 +356,7 @@ async fn test_stake_pool_deposit_with_wrong_validator_list_account() {
|
||||||
&user_stake,
|
&user_stake,
|
||||||
&authorized,
|
&authorized,
|
||||||
&lockup,
|
&lockup,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -475,6 +384,7 @@ async fn test_stake_pool_deposit_with_wrong_validator_list_account() {
|
||||||
&user_stake.pubkey(),
|
&user_stake.pubkey(),
|
||||||
&user_pool_account.pubkey(),
|
&user_pool_account.pubkey(),
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
|
&user,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.err()
|
.err()
|
||||||
|
@ -497,20 +407,18 @@ async fn test_stake_pool_deposit_to_unknown_validator() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let validator_stake_account = ValidatorStakeAccount::new_with_target_authority(
|
let validator_stake_account =
|
||||||
&stake_pool_accounts.deposit_authority,
|
ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey());
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
|
||||||
);
|
|
||||||
validator_stake_account
|
validator_stake_account
|
||||||
.create_and_delegate(
|
.create_and_delegate(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&stake_pool_accounts.owner,
|
&stake_pool_accounts.staker,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -531,8 +439,8 @@ async fn test_stake_pool_deposit_to_unknown_validator() {
|
||||||
let user_stake = Keypair::new();
|
let user_stake = Keypair::new();
|
||||||
let lockup = stake_program::Lockup::default();
|
let lockup = stake_program::Lockup::default();
|
||||||
let authorized = stake_program::Authorized {
|
let authorized = stake_program::Authorized {
|
||||||
staker: stake_pool_accounts.deposit_authority,
|
staker: user.pubkey(),
|
||||||
withdrawer: stake_pool_accounts.deposit_authority,
|
withdrawer: user.pubkey(),
|
||||||
};
|
};
|
||||||
create_independent_stake_account(
|
create_independent_stake_account(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
|
@ -541,6 +449,7 @@ async fn test_stake_pool_deposit_to_unknown_validator() {
|
||||||
&user_stake,
|
&user_stake,
|
||||||
&authorized,
|
&authorized,
|
||||||
&lockup,
|
&lockup,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -552,6 +461,7 @@ async fn test_stake_pool_deposit_to_unknown_validator() {
|
||||||
&user_stake.pubkey(),
|
&user_stake.pubkey(),
|
||||||
&user_pool_account.pubkey(),
|
&user_pool_account.pubkey(),
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
|
&user,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.err()
|
.err()
|
||||||
|
@ -571,74 +481,6 @@ async fn test_stake_pool_deposit_to_unknown_validator() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_stake_pool_deposit_with_wrong_deposit_authority() {
|
|
||||||
let (
|
|
||||||
mut banks_client,
|
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
mut stake_pool_accounts,
|
|
||||||
validator_stake_account,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let user = Keypair::new();
|
|
||||||
// make stake account
|
|
||||||
let user_stake = Keypair::new();
|
|
||||||
let lockup = stake_program::Lockup::default();
|
|
||||||
let authorized = stake_program::Authorized {
|
|
||||||
staker: stake_pool_accounts.deposit_authority,
|
|
||||||
withdrawer: stake_pool_accounts.deposit_authority,
|
|
||||||
};
|
|
||||||
create_independent_stake_account(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_stake,
|
|
||||||
&authorized,
|
|
||||||
&lockup,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// make pool token account
|
|
||||||
let user_pool_account = Keypair::new();
|
|
||||||
create_token_account(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_pool_account,
|
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
|
||||||
&user.pubkey(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
stake_pool_accounts.deposit_authority = Keypair::new().pubkey();
|
|
||||||
|
|
||||||
let transaction_error = stake_pool_accounts
|
|
||||||
.deposit_stake(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_stake.pubkey(),
|
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
&validator_stake_account.stake_account,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.err()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
match transaction_error {
|
|
||||||
TransportError::TransactionError(TransactionError::InstructionError(
|
|
||||||
_,
|
|
||||||
InstructionError::Custom(error_index),
|
|
||||||
)) => {
|
|
||||||
let program_error = error::StakePoolError::InvalidProgramAddress as u32;
|
|
||||||
assert_eq!(error_index, program_error);
|
|
||||||
}
|
|
||||||
_ => panic!("Wrong error occurs while try to make a deposit with wrong deposit authority"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_stake_pool_deposit_with_wrong_withdraw_authority() {
|
async fn test_stake_pool_deposit_with_wrong_withdraw_authority() {
|
||||||
let (
|
let (
|
||||||
|
@ -654,8 +496,8 @@ async fn test_stake_pool_deposit_with_wrong_withdraw_authority() {
|
||||||
let user_stake = Keypair::new();
|
let user_stake = Keypair::new();
|
||||||
let lockup = stake_program::Lockup::default();
|
let lockup = stake_program::Lockup::default();
|
||||||
let authorized = stake_program::Authorized {
|
let authorized = stake_program::Authorized {
|
||||||
staker: stake_pool_accounts.deposit_authority,
|
staker: user.pubkey(),
|
||||||
withdrawer: stake_pool_accounts.deposit_authority,
|
withdrawer: user.pubkey(),
|
||||||
};
|
};
|
||||||
create_independent_stake_account(
|
create_independent_stake_account(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
|
@ -664,6 +506,7 @@ async fn test_stake_pool_deposit_with_wrong_withdraw_authority() {
|
||||||
&user_stake,
|
&user_stake,
|
||||||
&authorized,
|
&authorized,
|
||||||
&lockup,
|
&lockup,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -690,6 +533,7 @@ async fn test_stake_pool_deposit_with_wrong_withdraw_authority() {
|
||||||
&user_stake.pubkey(),
|
&user_stake.pubkey(),
|
||||||
&user_pool_account.pubkey(),
|
&user_pool_account.pubkey(),
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
|
&user,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.err()
|
.err()
|
||||||
|
@ -707,75 +551,18 @@ async fn test_stake_pool_deposit_with_wrong_withdraw_authority() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_stake_pool_deposit_with_wrong_set_deposit_authority() {
|
|
||||||
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake_account) =
|
|
||||||
setup().await;
|
|
||||||
|
|
||||||
let user = Keypair::new();
|
|
||||||
// make stake account
|
|
||||||
let user_stake = Keypair::new();
|
|
||||||
let lockup = stake_program::Lockup::default();
|
|
||||||
let authorized = stake_program::Authorized {
|
|
||||||
staker: Keypair::new().pubkey(),
|
|
||||||
withdrawer: stake_pool_accounts.deposit_authority,
|
|
||||||
};
|
|
||||||
create_independent_stake_account(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_stake,
|
|
||||||
&authorized,
|
|
||||||
&lockup,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
// make pool token account
|
|
||||||
let user_pool_account = Keypair::new();
|
|
||||||
create_token_account(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_pool_account,
|
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
|
||||||
&user.pubkey(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let transaction_error = stake_pool_accounts
|
|
||||||
.deposit_stake(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_stake.pubkey(),
|
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
&validator_stake_account.stake_account,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.err()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
match transaction_error {
|
|
||||||
TransportError::TransactionError(TransactionError::InstructionError(_, error)) => {
|
|
||||||
assert_eq!(error, InstructionError::MissingRequiredSignature);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
panic!("Wrong error occurs while try to make deposit with wrong set deposit authority")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_stake_pool_deposit_with_wrong_mint_for_receiver_acc() {
|
async fn test_stake_pool_deposit_with_wrong_mint_for_receiver_acc() {
|
||||||
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake_account) =
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake_account) =
|
||||||
setup().await;
|
setup().await;
|
||||||
|
|
||||||
// make stake account
|
// make stake account
|
||||||
|
let user = Keypair::new();
|
||||||
let user_stake = Keypair::new();
|
let user_stake = Keypair::new();
|
||||||
let lockup = stake_program::Lockup::default();
|
let lockup = stake_program::Lockup::default();
|
||||||
let authorized = stake_program::Authorized {
|
let authorized = stake_program::Authorized {
|
||||||
staker: stake_pool_accounts.deposit_authority,
|
staker: user.pubkey(),
|
||||||
withdrawer: stake_pool_accounts.deposit_authority,
|
withdrawer: user.pubkey(),
|
||||||
};
|
};
|
||||||
create_independent_stake_account(
|
create_independent_stake_account(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
|
@ -784,12 +571,13 @@ async fn test_stake_pool_deposit_with_wrong_mint_for_receiver_acc() {
|
||||||
&user_stake,
|
&user_stake,
|
||||||
&authorized,
|
&authorized,
|
||||||
&lockup,
|
&lockup,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let outside_mint = Keypair::new();
|
let outside_mint = Keypair::new();
|
||||||
let outside_withdraw_auth = Keypair::new();
|
let outside_withdraw_auth = Keypair::new();
|
||||||
let outside_owner = Keypair::new();
|
let outside_manager = Keypair::new();
|
||||||
let outside_pool_fee_acc = Keypair::new();
|
let outside_pool_fee_acc = Keypair::new();
|
||||||
|
|
||||||
create_mint(
|
create_mint(
|
||||||
|
@ -808,7 +596,7 @@ async fn test_stake_pool_deposit_with_wrong_mint_for_receiver_acc() {
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&outside_pool_fee_acc,
|
&outside_pool_fee_acc,
|
||||||
&outside_mint.pubkey(),
|
&outside_mint.pubkey(),
|
||||||
&outside_owner.pubkey(),
|
&outside_manager.pubkey(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -821,6 +609,7 @@ async fn test_stake_pool_deposit_with_wrong_mint_for_receiver_acc() {
|
||||||
&user_stake.pubkey(),
|
&user_stake.pubkey(),
|
||||||
&outside_pool_fee_acc.pubkey(),
|
&outside_pool_fee_acc.pubkey(),
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
|
&user,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.err()
|
.err()
|
||||||
|
@ -843,3 +632,180 @@ async fn test_deposit_with_uninitialized_validator_list() {} // TODO
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_deposit_with_out_of_dated_pool_balances() {} // TODO
|
async fn test_deposit_with_out_of_dated_pool_balances() {} // TODO
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn success_with_deposit_authority() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
|
let deposit_authority = Keypair::new();
|
||||||
|
let stake_pool_accounts = StakePoolAccounts::new_with_deposit_authority(deposit_authority);
|
||||||
|
stake_pool_accounts
|
||||||
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let validator_stake_account = simple_add_validator_to_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let user = Keypair::new();
|
||||||
|
let user_stake = Keypair::new();
|
||||||
|
let lockup = stake_program::Lockup::default();
|
||||||
|
let authorized = stake_program::Authorized {
|
||||||
|
staker: user.pubkey(),
|
||||||
|
withdrawer: user.pubkey(),
|
||||||
|
};
|
||||||
|
let _stake_lamports = create_independent_stake_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake,
|
||||||
|
&authorized,
|
||||||
|
&lockup,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
create_vote(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake_account.validator,
|
||||||
|
&validator_stake_account.vote,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
delegate_stake_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake.pubkey(),
|
||||||
|
&user,
|
||||||
|
&validator_stake_account.vote.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// make pool token account
|
||||||
|
let user_pool_account = Keypair::new();
|
||||||
|
create_token_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_pool_account,
|
||||||
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
|
&user.pubkey(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
stake_pool_accounts
|
||||||
|
.deposit_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake.pubkey(),
|
||||||
|
&user_pool_account.pubkey(),
|
||||||
|
&validator_stake_account.stake_account,
|
||||||
|
&user,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_without_deposit_authority_signature() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
|
let deposit_authority = Keypair::new();
|
||||||
|
let mut stake_pool_accounts = StakePoolAccounts::new_with_deposit_authority(deposit_authority);
|
||||||
|
stake_pool_accounts
|
||||||
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let validator_stake_account = simple_add_validator_to_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let user = Keypair::new();
|
||||||
|
let user_stake = Keypair::new();
|
||||||
|
let lockup = stake_program::Lockup::default();
|
||||||
|
let authorized = stake_program::Authorized {
|
||||||
|
staker: user.pubkey(),
|
||||||
|
withdrawer: user.pubkey(),
|
||||||
|
};
|
||||||
|
let _stake_lamports = create_independent_stake_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake,
|
||||||
|
&authorized,
|
||||||
|
&lockup,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
create_vote(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake_account.validator,
|
||||||
|
&validator_stake_account.vote,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
delegate_stake_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake.pubkey(),
|
||||||
|
&user,
|
||||||
|
&validator_stake_account.vote.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// make pool token account
|
||||||
|
let user_pool_account = Keypair::new();
|
||||||
|
create_token_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_pool_account,
|
||||||
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
|
&user.pubkey(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let wrong_depositor = Keypair::new();
|
||||||
|
stake_pool_accounts.deposit_authority = wrong_depositor.pubkey();
|
||||||
|
stake_pool_accounts.deposit_authority_keypair = Some(wrong_depositor);
|
||||||
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.deposit_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake.pubkey(),
|
||||||
|
&user_pool_account.pubkey(),
|
||||||
|
&validator_stake_account.stake_account,
|
||||||
|
&user,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
|
||||||
|
assert_eq!(
|
||||||
|
error_index,
|
||||||
|
error::StakePoolError::InvalidProgramAddress as u32
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error occurs while try to make a deposit with wrong stake program ID"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,384 @@
|
||||||
|
#![cfg(feature = "test-bpf")]
|
||||||
|
|
||||||
|
mod helpers;
|
||||||
|
|
||||||
|
use {
|
||||||
|
bincode::deserialize,
|
||||||
|
helpers::*,
|
||||||
|
solana_program::{
|
||||||
|
clock::Epoch, hash::Hash, instruction::InstructionError, pubkey::Pubkey,
|
||||||
|
system_instruction::SystemError,
|
||||||
|
},
|
||||||
|
solana_program_test::*,
|
||||||
|
solana_sdk::{
|
||||||
|
signature::{Keypair, Signer},
|
||||||
|
transaction::{Transaction, TransactionError},
|
||||||
|
},
|
||||||
|
spl_stake_pool::{error::StakePoolError, id, instruction, stake_program},
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn setup() -> (
|
||||||
|
BanksClient,
|
||||||
|
Keypair,
|
||||||
|
Hash,
|
||||||
|
StakePoolAccounts,
|
||||||
|
ValidatorStakeAccount,
|
||||||
|
u64,
|
||||||
|
) {
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
let reserve_lamports = 100_000_000_000;
|
||||||
|
stake_pool_accounts
|
||||||
|
.initialize_stake_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
reserve_lamports,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let validator_stake_account = simple_add_validator_to_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _deposit_info = simple_deposit(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
&validator_stake_account,
|
||||||
|
5_000_000,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
(
|
||||||
|
banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
validator_stake_account,
|
||||||
|
reserve_lamports,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn success() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
validator_stake,
|
||||||
|
reserve_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
// Save reserve stake
|
||||||
|
let pre_reserve_stake_account = get_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Check no transient stake
|
||||||
|
let transient_account = banks_client
|
||||||
|
.get_account(validator_stake.transient_stake_account)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(transient_account.is_none());
|
||||||
|
|
||||||
|
let rent = banks_client.get_rent().await.unwrap();
|
||||||
|
let lamports = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
|
||||||
|
let reserve_lamports = reserve_lamports - lamports;
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.increase_validator_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
&validator_stake.vote.pubkey(),
|
||||||
|
reserve_lamports,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
// Check reserve stake account balance
|
||||||
|
let reserve_stake_account = get_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let reserve_stake_state =
|
||||||
|
deserialize::<stake_program::StakeState>(&reserve_stake_account.data).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
pre_reserve_stake_account.lamports - reserve_lamports,
|
||||||
|
reserve_stake_account.lamports
|
||||||
|
);
|
||||||
|
assert!(reserve_stake_state.delegation().is_none());
|
||||||
|
|
||||||
|
// Check transient stake account state and balance
|
||||||
|
let transient_stake_account =
|
||||||
|
get_account(&mut banks_client, &validator_stake.transient_stake_account).await;
|
||||||
|
let transient_stake_state =
|
||||||
|
deserialize::<stake_program::StakeState>(&transient_stake_account.data).unwrap();
|
||||||
|
assert_eq!(transient_stake_account.lamports, reserve_lamports);
|
||||||
|
assert_ne!(
|
||||||
|
transient_stake_state.delegation().unwrap().activation_epoch,
|
||||||
|
Epoch::MAX
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_wrong_withdraw_authority() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
validator_stake,
|
||||||
|
reserve_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
let wrong_authority = Pubkey::new_unique();
|
||||||
|
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&[instruction::increase_validator_stake(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&wrong_authority,
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
&validator_stake.vote.pubkey(),
|
||||||
|
reserve_lamports / 2,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer, &stake_pool_accounts.staker],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
let error = banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
|
||||||
|
let program_error = StakePoolError::InvalidProgramAddress as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_wrong_validator_list() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
validator_stake,
|
||||||
|
reserve_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
let wrong_validator_list = Pubkey::new_unique();
|
||||||
|
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&[instruction::increase_validator_stake(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&stake_pool_accounts.withdraw_authority,
|
||||||
|
&wrong_validator_list,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
&validator_stake.vote.pubkey(),
|
||||||
|
reserve_lamports / 2,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer, &stake_pool_accounts.staker],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
let error = banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
|
||||||
|
let program_error = StakePoolError::InvalidValidatorStakeList as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_unknown_validator() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
_validator_stake,
|
||||||
|
reserve_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
let unknown_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey());
|
||||||
|
unknown_stake
|
||||||
|
.create_and_delegate(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts.staker,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&[instruction::increase_validator_stake(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&stake_pool_accounts.withdraw_authority,
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&unknown_stake.transient_stake_account,
|
||||||
|
&unknown_stake.vote.pubkey(),
|
||||||
|
reserve_lamports / 2,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer, &stake_pool_accounts.staker],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
let error = banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
|
||||||
|
let program_error = StakePoolError::ValidatorNotFound as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_increase_twice() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
validator_stake,
|
||||||
|
reserve_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.increase_validator_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
&validator_stake.vote.pubkey(),
|
||||||
|
reserve_lamports / 3,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.increase_validator_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
&validator_stake.vote.pubkey(),
|
||||||
|
reserve_lamports / 4,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
|
||||||
|
let program_error = SystemError::AccountAlreadyInUse as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_small_lamport_amount() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
validator_stake,
|
||||||
|
_reserve_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
let rent = banks_client.get_rent().await.unwrap();
|
||||||
|
let lamports = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
|
||||||
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.increase_validator_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
&validator_stake.vote.pubkey(),
|
||||||
|
lamports,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::InvalidError) => {}
|
||||||
|
_ => panic!("Wrong error"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_overdraw_reserve() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
validator_stake,
|
||||||
|
reserve_lamports,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.increase_validator_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
&validator_stake.vote.pubkey(),
|
||||||
|
reserve_lamports,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::InsufficientFunds) => {}
|
||||||
|
_ => panic!("Wrong error occurs while overdrawing reserve stake"),
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,13 +3,14 @@
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
borsh::BorshSerialize,
|
borsh::{BorshDeserialize, BorshSerialize},
|
||||||
helpers::*,
|
helpers::*,
|
||||||
solana_program::{
|
solana_program::{
|
||||||
borsh::get_packed_len,
|
borsh::get_packed_len,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
instruction::{AccountMeta, Instruction},
|
instruction::{AccountMeta, Instruction},
|
||||||
program_pack::Pack,
|
program_pack::Pack,
|
||||||
|
pubkey::Pubkey,
|
||||||
system_instruction, sysvar,
|
system_instruction, sysvar,
|
||||||
},
|
},
|
||||||
solana_program_test::*,
|
solana_program_test::*,
|
||||||
|
@ -19,11 +20,11 @@ use {
|
||||||
},
|
},
|
||||||
spl_stake_pool::{
|
spl_stake_pool::{
|
||||||
borsh::{get_instance_packed_len, try_from_slice_unchecked},
|
borsh::{get_instance_packed_len, try_from_slice_unchecked},
|
||||||
error, id, instruction, state,
|
error, id, instruction, stake_program, state,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async fn create_mint_and_token_account(
|
async fn create_required_accounts(
|
||||||
banks_client: &mut BanksClient,
|
banks_client: &mut BanksClient,
|
||||||
payer: &Keypair,
|
payer: &Keypair,
|
||||||
recent_blockhash: &Hash,
|
recent_blockhash: &Hash,
|
||||||
|
@ -45,18 +46,32 @@ async fn create_mint_and_token_account(
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
&stake_pool_accounts.pool_fee_account,
|
&stake_pool_accounts.pool_fee_account,
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
&stake_pool_accounts.manager.pubkey(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
create_independent_stake_account(
|
||||||
|
banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts.reserve_stake,
|
||||||
|
&stake_program::Authorized {
|
||||||
|
staker: stake_pool_accounts.withdraw_authority,
|
||||||
|
withdrawer: stake_pool_accounts.withdraw_authority,
|
||||||
|
},
|
||||||
|
&stake_program::Lockup::default(),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_stake_pool_initialize() {
|
async fn success() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -77,11 +92,11 @@ async fn test_stake_pool_initialize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_initialize_already_initialized_stake_pool() {
|
async fn fail_double_initialize() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -91,7 +106,7 @@ async fn test_initialize_already_initialized_stake_pool() {
|
||||||
second_stake_pool_accounts.stake_pool = stake_pool_accounts.stake_pool;
|
second_stake_pool_accounts.stake_pool = stake_pool_accounts.stake_pool;
|
||||||
|
|
||||||
let transaction_error = second_stake_pool_accounts
|
let transaction_error = second_stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &latest_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &latest_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.err()
|
.err()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -108,11 +123,11 @@ async fn test_initialize_already_initialized_stake_pool() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_initialize_stake_pool_with_already_initialized_stake_list_storage() {
|
async fn fail_with_already_initialized_validator_list() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -122,7 +137,7 @@ async fn test_initialize_stake_pool_with_already_initialized_stake_list_storage(
|
||||||
second_stake_pool_accounts.validator_list = stake_pool_accounts.validator_list;
|
second_stake_pool_accounts.validator_list = stake_pool_accounts.validator_list;
|
||||||
|
|
||||||
let transaction_error = second_stake_pool_accounts
|
let transaction_error = second_stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &latest_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &latest_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.err()
|
.err()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -139,16 +154,16 @@ async fn test_initialize_stake_pool_with_already_initialized_stake_list_storage(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_initialize_stake_pool_with_high_fee() {
|
async fn fail_with_high_fee() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let mut stake_pool_accounts = StakePoolAccounts::new();
|
let mut stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts.fee = instruction::Fee {
|
stake_pool_accounts.fee = state::Fee {
|
||||||
numerator: 100001,
|
numerator: 100001,
|
||||||
denominator: 100000,
|
denominator: 100000,
|
||||||
};
|
};
|
||||||
|
|
||||||
let transaction_error = stake_pool_accounts
|
let transaction_error = stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.err()
|
.err()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -165,11 +180,11 @@ async fn test_initialize_stake_pool_with_high_fee() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_initialize_stake_pool_with_wrong_max_validators() {
|
async fn fail_with_wrong_max_validators() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
|
||||||
create_mint_and_token_account(
|
create_required_accounts(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
|
@ -204,15 +219,17 @@ async fn test_initialize_stake_pool_with_wrong_max_validators() {
|
||||||
instruction::initialize(
|
instruction::initialize(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
&stake_pool_accounts.manager.pubkey(),
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
&stake_pool_accounts.validator_list.pubkey(),
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
&stake_pool_accounts.pool_fee_account.pubkey(),
|
&stake_pool_accounts.pool_fee_account.pubkey(),
|
||||||
&spl_token::id(),
|
&spl_token::id(),
|
||||||
stake_pool_accounts.fee.clone(),
|
None,
|
||||||
|
stake_pool_accounts.fee,
|
||||||
stake_pool_accounts.max_validators,
|
stake_pool_accounts.max_validators,
|
||||||
)
|
),
|
||||||
.unwrap(),
|
|
||||||
],
|
],
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
|
@ -221,7 +238,7 @@ async fn test_initialize_stake_pool_with_wrong_max_validators() {
|
||||||
&payer,
|
&payer,
|
||||||
&stake_pool_accounts.stake_pool,
|
&stake_pool_accounts.stake_pool,
|
||||||
&stake_pool_accounts.validator_list,
|
&stake_pool_accounts.validator_list,
|
||||||
&stake_pool_accounts.owner,
|
&stake_pool_accounts.manager,
|
||||||
],
|
],
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
);
|
);
|
||||||
|
@ -244,12 +261,12 @@ async fn test_initialize_stake_pool_with_wrong_max_validators() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_initialize_stake_pool_with_wrong_mint_authority() {
|
async fn fail_with_wrong_mint_authority() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
let wrong_mint = Keypair::new();
|
let wrong_mint = Keypair::new();
|
||||||
|
|
||||||
create_mint_and_token_account(
|
create_required_accounts(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
|
@ -274,9 +291,12 @@ async fn test_initialize_stake_pool_with_wrong_mint_authority() {
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&stake_pool_accounts.stake_pool,
|
&stake_pool_accounts.stake_pool,
|
||||||
&stake_pool_accounts.validator_list,
|
&stake_pool_accounts.validator_list,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
&wrong_mint.pubkey(),
|
&wrong_mint.pubkey(),
|
||||||
&stake_pool_accounts.pool_fee_account.pubkey(),
|
&stake_pool_accounts.pool_fee_account.pubkey(),
|
||||||
&stake_pool_accounts.owner,
|
&stake_pool_accounts.manager,
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&None,
|
||||||
&stake_pool_accounts.fee,
|
&stake_pool_accounts.fee,
|
||||||
stake_pool_accounts.max_validators,
|
stake_pool_accounts.max_validators,
|
||||||
)
|
)
|
||||||
|
@ -297,7 +317,7 @@ async fn test_initialize_stake_pool_with_wrong_mint_authority() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_initialize_stake_pool_with_wrong_token_program_id() {
|
async fn fail_with_wrong_token_program_id() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
|
||||||
|
@ -358,15 +378,17 @@ async fn test_initialize_stake_pool_with_wrong_token_program_id() {
|
||||||
instruction::initialize(
|
instruction::initialize(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
&stake_pool_accounts.manager.pubkey(),
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
&stake_pool_accounts.validator_list.pubkey(),
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
&stake_pool_accounts.pool_fee_account.pubkey(),
|
&stake_pool_accounts.pool_fee_account.pubkey(),
|
||||||
&wrong_token_program.pubkey(),
|
&wrong_token_program.pubkey(),
|
||||||
stake_pool_accounts.fee.clone(),
|
None,
|
||||||
|
stake_pool_accounts.fee,
|
||||||
stake_pool_accounts.max_validators,
|
stake_pool_accounts.max_validators,
|
||||||
)
|
),
|
||||||
.unwrap(),
|
|
||||||
],
|
],
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
|
@ -375,7 +397,7 @@ async fn test_initialize_stake_pool_with_wrong_token_program_id() {
|
||||||
&payer,
|
&payer,
|
||||||
&stake_pool_accounts.stake_pool,
|
&stake_pool_accounts.stake_pool,
|
||||||
&stake_pool_accounts.validator_list,
|
&stake_pool_accounts.validator_list,
|
||||||
&stake_pool_accounts.owner,
|
&stake_pool_accounts.manager,
|
||||||
],
|
],
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
);
|
);
|
||||||
|
@ -396,7 +418,7 @@ async fn test_initialize_stake_pool_with_wrong_token_program_id() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_initialize_stake_pool_with_wrong_fee_accounts_owner() {
|
async fn fail_with_wrong_fee_account() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
|
||||||
|
@ -434,39 +456,35 @@ async fn test_initialize_stake_pool_with_wrong_fee_accounts_owner() {
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&stake_pool_accounts.stake_pool,
|
&stake_pool_accounts.stake_pool,
|
||||||
&stake_pool_accounts.validator_list,
|
&stake_pool_accounts.validator_list,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
&stake_pool_accounts.pool_fee_account.pubkey(),
|
&stake_pool_accounts.pool_fee_account.pubkey(),
|
||||||
&stake_pool_accounts.owner,
|
&stake_pool_accounts.manager,
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&None,
|
||||||
&stake_pool_accounts.fee,
|
&stake_pool_accounts.fee,
|
||||||
stake_pool_accounts.max_validators,
|
stake_pool_accounts.max_validators,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.err()
|
.err()
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
match transaction_error {
|
assert_eq!(
|
||||||
TransportError::TransactionError(TransactionError::InstructionError(
|
transaction_error,
|
||||||
_,
|
TransactionError::InstructionError(2, InstructionError::IncorrectProgramId)
|
||||||
InstructionError::Custom(error_index),
|
);
|
||||||
)) => {
|
|
||||||
let program_error = error::StakePoolError::InvalidFeeAccount as u32;
|
|
||||||
assert_eq!(error_index, program_error);
|
|
||||||
}
|
|
||||||
_ => panic!(
|
|
||||||
"Wrong error occurs while try to initialize stake pool with wrong fee account's owner"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_initialize_stake_pool_with_wrong_withdraw_authority() {
|
async fn fail_with_wrong_withdraw_authority() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let mut stake_pool_accounts = StakePoolAccounts::new();
|
let mut stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
|
||||||
stake_pool_accounts.withdraw_authority = Keypair::new().pubkey();
|
stake_pool_accounts.withdraw_authority = Keypair::new().pubkey();
|
||||||
|
|
||||||
let transaction_error = stake_pool_accounts
|
let transaction_error = stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.err()
|
.err()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -486,11 +504,11 @@ async fn test_initialize_stake_pool_with_wrong_withdraw_authority() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_initialize_stake_pool_with_not_rent_exempt_pool() {
|
async fn fail_with_not_rent_exempt_pool() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
|
||||||
create_mint_and_token_account(
|
create_required_accounts(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
|
@ -524,15 +542,17 @@ async fn test_initialize_stake_pool_with_not_rent_exempt_pool() {
|
||||||
instruction::initialize(
|
instruction::initialize(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
&stake_pool_accounts.manager.pubkey(),
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
&stake_pool_accounts.validator_list.pubkey(),
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
&stake_pool_accounts.pool_fee_account.pubkey(),
|
&stake_pool_accounts.pool_fee_account.pubkey(),
|
||||||
&spl_token::id(),
|
&spl_token::id(),
|
||||||
stake_pool_accounts.fee.clone(),
|
None,
|
||||||
|
stake_pool_accounts.fee,
|
||||||
stake_pool_accounts.max_validators,
|
stake_pool_accounts.max_validators,
|
||||||
)
|
),
|
||||||
.unwrap(),
|
|
||||||
],
|
],
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
|
@ -541,7 +561,7 @@ async fn test_initialize_stake_pool_with_not_rent_exempt_pool() {
|
||||||
&payer,
|
&payer,
|
||||||
&stake_pool_accounts.stake_pool,
|
&stake_pool_accounts.stake_pool,
|
||||||
&stake_pool_accounts.validator_list,
|
&stake_pool_accounts.validator_list,
|
||||||
&stake_pool_accounts.owner,
|
&stake_pool_accounts.manager,
|
||||||
],
|
],
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
);
|
);
|
||||||
|
@ -558,11 +578,11 @@ async fn test_initialize_stake_pool_with_not_rent_exempt_pool() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_initialize_stake_pool_with_not_rent_exempt_validator_list() {
|
async fn fail_with_not_rent_exempt_validator_list() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
|
||||||
create_mint_and_token_account(
|
create_required_accounts(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
|
@ -596,15 +616,17 @@ async fn test_initialize_stake_pool_with_not_rent_exempt_validator_list() {
|
||||||
instruction::initialize(
|
instruction::initialize(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
&stake_pool_accounts.manager.pubkey(),
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
&stake_pool_accounts.validator_list.pubkey(),
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
&stake_pool_accounts.pool_fee_account.pubkey(),
|
&stake_pool_accounts.pool_fee_account.pubkey(),
|
||||||
&spl_token::id(),
|
&spl_token::id(),
|
||||||
stake_pool_accounts.fee.clone(),
|
None,
|
||||||
|
stake_pool_accounts.fee,
|
||||||
stake_pool_accounts.max_validators,
|
stake_pool_accounts.max_validators,
|
||||||
)
|
),
|
||||||
.unwrap(),
|
|
||||||
],
|
],
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
|
@ -613,7 +635,7 @@ async fn test_initialize_stake_pool_with_not_rent_exempt_validator_list() {
|
||||||
&payer,
|
&payer,
|
||||||
&stake_pool_accounts.stake_pool,
|
&stake_pool_accounts.stake_pool,
|
||||||
&stake_pool_accounts.validator_list,
|
&stake_pool_accounts.validator_list,
|
||||||
&stake_pool_accounts.owner,
|
&stake_pool_accounts.manager,
|
||||||
],
|
],
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
);
|
);
|
||||||
|
@ -632,11 +654,11 @@ async fn test_initialize_stake_pool_with_not_rent_exempt_validator_list() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_initialize_stake_pool_without_owner_signature() {
|
async fn fail_without_manager_signature() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
|
||||||
create_mint_and_token_account(
|
create_required_accounts(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
|
@ -653,14 +675,16 @@ async fn test_initialize_stake_pool_without_owner_signature() {
|
||||||
let rent_validator_list = rent.minimum_balance(validator_list_size);
|
let rent_validator_list = rent.minimum_balance(validator_list_size);
|
||||||
|
|
||||||
let init_data = instruction::StakePoolInstruction::Initialize {
|
let init_data = instruction::StakePoolInstruction::Initialize {
|
||||||
fee: stake_pool_accounts.fee.clone(),
|
fee: stake_pool_accounts.fee,
|
||||||
max_validators: stake_pool_accounts.max_validators,
|
max_validators: stake_pool_accounts.max_validators,
|
||||||
};
|
};
|
||||||
let data = init_data.try_to_vec().unwrap();
|
let data = init_data.try_to_vec().unwrap();
|
||||||
let accounts = vec![
|
let accounts = vec![
|
||||||
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), true),
|
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), true),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.owner.pubkey(), false),
|
AccountMeta::new_readonly(stake_pool_accounts.manager.pubkey(), false),
|
||||||
|
AccountMeta::new_readonly(stake_pool_accounts.staker.pubkey(), false),
|
||||||
AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
|
AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
|
||||||
|
AccountMeta::new_readonly(stake_pool_accounts.reserve_stake.pubkey(), false),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.pool_mint.pubkey(), false),
|
AccountMeta::new_readonly(stake_pool_accounts.pool_mint.pubkey(), false),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.pool_fee_account.pubkey(), false),
|
AccountMeta::new_readonly(stake_pool_accounts.pool_fee_account.pubkey(), false),
|
||||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
|
@ -716,7 +740,298 @@ async fn test_initialize_stake_pool_without_owner_signature() {
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!(
|
_ => panic!(
|
||||||
"Wrong error occurs while try to initialize stake pool without owner's signature"
|
"Wrong error occurs while try to initialize stake pool without manager's signature"
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_pre_minted_pool_tokens() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
let mint_authority = Keypair::new();
|
||||||
|
|
||||||
|
create_mint(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts.pool_mint,
|
||||||
|
&mint_authority.pubkey(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
create_token_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts.pool_fee_account,
|
||||||
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
|
&stake_pool_accounts.manager.pubkey(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
mint_tokens(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
|
&stake_pool_accounts.pool_fee_account.pubkey(),
|
||||||
|
&mint_authority,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let transaction_error = create_stake_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts.stake_pool,
|
||||||
|
&stake_pool_accounts.validator_list,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
|
&stake_pool_accounts.pool_fee_account.pubkey(),
|
||||||
|
&stake_pool_accounts.manager,
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&None,
|
||||||
|
&stake_pool_accounts.fee,
|
||||||
|
stake_pool_accounts.max_validators,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match transaction_error {
|
||||||
|
TransportError::TransactionError(TransactionError::InstructionError(
|
||||||
|
_,
|
||||||
|
InstructionError::Custom(error_index),
|
||||||
|
)) => {
|
||||||
|
let program_error = error::StakePoolError::NonZeroPoolTokenSupply as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error occurs while try to initialize stake pool with wrong mint authority of pool fee account"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_bad_reserve() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
let wrong_authority = Pubkey::new_unique();
|
||||||
|
|
||||||
|
create_required_accounts(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
{
|
||||||
|
let bad_stake = Keypair::new();
|
||||||
|
create_independent_stake_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&bad_stake,
|
||||||
|
&stake_program::Authorized {
|
||||||
|
staker: wrong_authority,
|
||||||
|
withdrawer: stake_pool_accounts.withdraw_authority,
|
||||||
|
},
|
||||||
|
&stake_program::Lockup::default(),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let error = create_stake_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts.stake_pool,
|
||||||
|
&stake_pool_accounts.validator_list,
|
||||||
|
&bad_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
|
&stake_pool_accounts.pool_fee_account.pubkey(),
|
||||||
|
&stake_pool_accounts.manager,
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&None,
|
||||||
|
&stake_pool_accounts.fee,
|
||||||
|
stake_pool_accounts.max_validators,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
error,
|
||||||
|
TransactionError::InstructionError(
|
||||||
|
2,
|
||||||
|
InstructionError::Custom(error::StakePoolError::WrongStakeState as u32),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let bad_stake = Keypair::new();
|
||||||
|
create_independent_stake_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&bad_stake,
|
||||||
|
&stake_program::Authorized {
|
||||||
|
staker: stake_pool_accounts.withdraw_authority,
|
||||||
|
withdrawer: wrong_authority,
|
||||||
|
},
|
||||||
|
&stake_program::Lockup::default(),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let error = create_stake_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts.stake_pool,
|
||||||
|
&stake_pool_accounts.validator_list,
|
||||||
|
&bad_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
|
&stake_pool_accounts.pool_fee_account.pubkey(),
|
||||||
|
&stake_pool_accounts.manager,
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&None,
|
||||||
|
&stake_pool_accounts.fee,
|
||||||
|
stake_pool_accounts.max_validators,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
error,
|
||||||
|
TransactionError::InstructionError(
|
||||||
|
2,
|
||||||
|
InstructionError::Custom(error::StakePoolError::WrongStakeState as u32),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let bad_stake = Keypair::new();
|
||||||
|
create_independent_stake_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&bad_stake,
|
||||||
|
&stake_program::Authorized {
|
||||||
|
staker: stake_pool_accounts.withdraw_authority,
|
||||||
|
withdrawer: stake_pool_accounts.withdraw_authority,
|
||||||
|
},
|
||||||
|
&stake_program::Lockup {
|
||||||
|
custodian: wrong_authority,
|
||||||
|
..stake_program::Lockup::default()
|
||||||
|
},
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let error = create_stake_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts.stake_pool,
|
||||||
|
&stake_pool_accounts.validator_list,
|
||||||
|
&bad_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
|
&stake_pool_accounts.pool_fee_account.pubkey(),
|
||||||
|
&stake_pool_accounts.manager,
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&None,
|
||||||
|
&stake_pool_accounts.fee,
|
||||||
|
stake_pool_accounts.max_validators,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
error,
|
||||||
|
TransactionError::InstructionError(
|
||||||
|
2,
|
||||||
|
InstructionError::Custom(error::StakePoolError::WrongStakeState as u32),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let bad_stake = Keypair::new();
|
||||||
|
let rent = banks_client.get_rent().await.unwrap();
|
||||||
|
let lamports = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
|
||||||
|
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&[system_instruction::create_account(
|
||||||
|
&payer.pubkey(),
|
||||||
|
&bad_stake.pubkey(),
|
||||||
|
lamports,
|
||||||
|
std::mem::size_of::<stake_program::StakeState>() as u64,
|
||||||
|
&stake_program::id(),
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer, &bad_stake],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
|
let error = create_stake_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts.stake_pool,
|
||||||
|
&stake_pool_accounts.validator_list,
|
||||||
|
&bad_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
|
&stake_pool_accounts.pool_fee_account.pubkey(),
|
||||||
|
&stake_pool_accounts.manager,
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&None,
|
||||||
|
&stake_pool_accounts.fee,
|
||||||
|
stake_pool_accounts.max_validators,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
error,
|
||||||
|
TransactionError::InstructionError(
|
||||||
|
2,
|
||||||
|
InstructionError::Custom(error::StakePoolError::WrongStakeState as u32),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn success_with_required_deposit_authority() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
|
let deposit_authority = Keypair::new();
|
||||||
|
let stake_pool_accounts = StakePoolAccounts::new_with_deposit_authority(deposit_authority);
|
||||||
|
stake_pool_accounts
|
||||||
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Stake pool now exists
|
||||||
|
let stake_pool_account =
|
||||||
|
get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
|
||||||
|
let stake_pool = state::StakePool::try_from_slice(stake_pool_account.data.as_slice()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
stake_pool.deposit_authority,
|
||||||
|
stake_pool_accounts.deposit_authority
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
#![cfg(feature = "test-bpf")]
|
||||||
|
|
||||||
|
mod helpers;
|
||||||
|
|
||||||
|
use {
|
||||||
|
borsh::BorshDeserialize,
|
||||||
|
helpers::*,
|
||||||
|
solana_program::hash::Hash,
|
||||||
|
solana_program_test::*,
|
||||||
|
solana_sdk::{
|
||||||
|
instruction::InstructionError, signature::Keypair, signature::Signer,
|
||||||
|
transaction::Transaction, transaction::TransactionError,
|
||||||
|
},
|
||||||
|
spl_stake_pool::{
|
||||||
|
error, id, instruction,
|
||||||
|
state::{Fee, StakePool},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn setup() -> (BanksClient, Keypair, Hash, StakePoolAccounts, Fee) {
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
stake_pool_accounts
|
||||||
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let new_fee = Fee {
|
||||||
|
numerator: 10,
|
||||||
|
denominator: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
new_fee,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn success() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_fee) = setup().await;
|
||||||
|
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&[instruction::set_fee(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&stake_pool_accounts.manager.pubkey(),
|
||||||
|
new_fee,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer, &stake_pool_accounts.manager],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
|
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
|
||||||
|
let stake_pool = StakePool::try_from_slice(&stake_pool.data.as_slice()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(stake_pool.fee, new_fee);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_wrong_manager() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_fee) = setup().await;
|
||||||
|
|
||||||
|
let wrong_manager = Keypair::new();
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&[instruction::set_fee(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&wrong_manager.pubkey(),
|
||||||
|
new_fee,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer, &wrong_manager],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
let error = banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
|
||||||
|
let program_error = error::StakePoolError::WrongManager as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error occurs while malicious try to set manager"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_bad_fee() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, _new_fee) = setup().await;
|
||||||
|
|
||||||
|
let new_fee = Fee {
|
||||||
|
numerator: 11,
|
||||||
|
denominator: 10,
|
||||||
|
};
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&[instruction::set_fee(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&stake_pool_accounts.manager.pubkey(),
|
||||||
|
new_fee,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer, &stake_pool_accounts.manager],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
|
let error = banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
|
||||||
|
let program_error = error::StakePoolError::FeeTooHigh as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error occurs while malicious try to set manager"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_not_updated() {
|
||||||
|
let mut context = program_test().start_with_context().await;
|
||||||
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
stake_pool_accounts
|
||||||
|
.initialize_stake_pool(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let new_fee = Fee {
|
||||||
|
numerator: 10,
|
||||||
|
denominator: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
// move forward so an update is required
|
||||||
|
context.warp_to_slot(50_000).unwrap();
|
||||||
|
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&[instruction::set_fee(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&stake_pool_accounts.manager.pubkey(),
|
||||||
|
new_fee,
|
||||||
|
)],
|
||||||
|
Some(&context.payer.pubkey()),
|
||||||
|
&[&context.payer, &stake_pool_accounts.manager],
|
||||||
|
context.last_blockhash,
|
||||||
|
);
|
||||||
|
let error = context
|
||||||
|
.banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
|
||||||
|
let program_error = error::StakePoolError::StakeListAndPoolOutOfDate as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error occurs while malicious try to set manager"),
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,19 +28,19 @@ async fn setup() -> (
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_pool_fee = Keypair::new();
|
let new_pool_fee = Keypair::new();
|
||||||
let new_owner = Keypair::new();
|
let new_manager = Keypair::new();
|
||||||
create_token_account(
|
create_token_account(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&new_pool_fee,
|
&new_pool_fee,
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
&new_owner.pubkey(),
|
&new_manager.pubkey(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -51,52 +51,50 @@ async fn setup() -> (
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
stake_pool_accounts,
|
stake_pool_accounts,
|
||||||
new_pool_fee,
|
new_pool_fee,
|
||||||
new_owner,
|
new_manager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_set_owner() {
|
async fn test_set_manager() {
|
||||||
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_pool_fee, new_owner) =
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_pool_fee, new_manager) =
|
||||||
setup().await;
|
setup().await;
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(
|
let mut transaction = Transaction::new_with_payer(
|
||||||
&[instruction::set_owner(
|
&[instruction::set_manager(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
&stake_pool_accounts.manager.pubkey(),
|
||||||
&new_owner.pubkey(),
|
&new_manager.pubkey(),
|
||||||
&new_pool_fee.pubkey(),
|
&new_pool_fee.pubkey(),
|
||||||
)
|
)],
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
transaction.sign(&[&payer, &stake_pool_accounts.manager], recent_blockhash);
|
||||||
banks_client.process_transaction(transaction).await.unwrap();
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
|
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
|
||||||
let stake_pool = state::StakePool::try_from_slice(&stake_pool.data.as_slice()).unwrap();
|
let stake_pool = state::StakePool::try_from_slice(&stake_pool.data.as_slice()).unwrap();
|
||||||
|
|
||||||
assert_eq!(stake_pool.owner, new_owner.pubkey());
|
assert_eq!(stake_pool.manager, new_manager.pubkey());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_set_owner_by_malicious() {
|
async fn test_set_manager_by_malicious() {
|
||||||
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_pool_fee, new_owner) =
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_pool_fee, new_manager) =
|
||||||
setup().await;
|
setup().await;
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(
|
let mut transaction = Transaction::new_with_payer(
|
||||||
&[instruction::set_owner(
|
&[instruction::set_manager(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&new_owner.pubkey(),
|
&new_manager.pubkey(),
|
||||||
&new_owner.pubkey(),
|
&new_manager.pubkey(),
|
||||||
&new_pool_fee.pubkey(),
|
&new_pool_fee.pubkey(),
|
||||||
)
|
)],
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
transaction.sign(&[&payer, &new_owner], recent_blockhash);
|
transaction.sign(&[&payer, &new_manager], recent_blockhash);
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
@ -108,25 +106,25 @@ async fn test_set_owner_by_malicious() {
|
||||||
_,
|
_,
|
||||||
InstructionError::Custom(error_index),
|
InstructionError::Custom(error_index),
|
||||||
)) => {
|
)) => {
|
||||||
let program_error = error::StakePoolError::WrongOwner as u32;
|
let program_error = error::StakePoolError::WrongManager as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong error occurs while malicious try to set owner"),
|
_ => panic!("Wrong error occurs while malicious try to set manager"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_set_owner_without_signature() {
|
async fn test_set_manager_without_signature() {
|
||||||
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_pool_fee, new_owner) =
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_pool_fee, new_manager) =
|
||||||
setup().await;
|
setup().await;
|
||||||
|
|
||||||
let data = instruction::StakePoolInstruction::SetOwner
|
let data = instruction::StakePoolInstruction::SetManager
|
||||||
.try_to_vec()
|
.try_to_vec()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let accounts = vec![
|
let accounts = vec![
|
||||||
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
|
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.owner.pubkey(), false),
|
AccountMeta::new_readonly(stake_pool_accounts.manager.pubkey(), false),
|
||||||
AccountMeta::new_readonly(new_owner.pubkey(), false),
|
AccountMeta::new_readonly(new_manager.pubkey(), false),
|
||||||
AccountMeta::new_readonly(new_pool_fee.pubkey(), false),
|
AccountMeta::new_readonly(new_pool_fee.pubkey(), false),
|
||||||
];
|
];
|
||||||
let instruction = Instruction {
|
let instruction = Instruction {
|
||||||
|
@ -151,23 +149,23 @@ async fn test_set_owner_without_signature() {
|
||||||
let program_error = error::StakePoolError::SignatureMissing as u32;
|
let program_error = error::StakePoolError::SignatureMissing as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong error occurs while try to set new owner without signature"),
|
_ => panic!("Wrong error occurs while try to set new manager without signature"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_set_owner_with_wrong_mint_for_pool_fee_acc() {
|
async fn test_set_manager_with_wrong_mint_for_pool_fee_acc() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_mint = Keypair::new();
|
let new_mint = Keypair::new();
|
||||||
let new_withdraw_auth = Keypair::new();
|
let new_withdraw_auth = Keypair::new();
|
||||||
let new_pool_fee = Keypair::new();
|
let new_pool_fee = Keypair::new();
|
||||||
let new_owner = Keypair::new();
|
let new_manager = Keypair::new();
|
||||||
|
|
||||||
create_mint(
|
create_mint(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
|
@ -184,23 +182,22 @@ async fn test_set_owner_with_wrong_mint_for_pool_fee_acc() {
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&new_pool_fee,
|
&new_pool_fee,
|
||||||
&new_mint.pubkey(),
|
&new_mint.pubkey(),
|
||||||
&new_owner.pubkey(),
|
&new_manager.pubkey(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(
|
let mut transaction = Transaction::new_with_payer(
|
||||||
&[instruction::set_owner(
|
&[instruction::set_manager(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
&stake_pool_accounts.manager.pubkey(),
|
||||||
&new_owner.pubkey(),
|
&new_manager.pubkey(),
|
||||||
&new_pool_fee.pubkey(),
|
&new_pool_fee.pubkey(),
|
||||||
)
|
)],
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
transaction.sign(&[&payer, &stake_pool_accounts.manager], recent_blockhash);
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
@ -215,6 +212,6 @@ async fn test_set_owner_with_wrong_mint_for_pool_fee_acc() {
|
||||||
let program_error = error::StakePoolError::WrongAccountMint as u32;
|
let program_error = error::StakePoolError::WrongAccountMint as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong error occurs while try to set new owner with wrong mint"),
|
_ => panic!("Wrong error occurs while try to set new manager with wrong mint"),
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
#![cfg(feature = "test-bpf")]
|
||||||
|
|
||||||
|
mod helpers;
|
||||||
|
|
||||||
|
use {
|
||||||
|
borsh::{BorshDeserialize, BorshSerialize},
|
||||||
|
helpers::*,
|
||||||
|
solana_program::{
|
||||||
|
hash::Hash,
|
||||||
|
instruction::{AccountMeta, Instruction},
|
||||||
|
},
|
||||||
|
solana_program_test::*,
|
||||||
|
solana_sdk::{
|
||||||
|
instruction::InstructionError, signature::Keypair, signature::Signer,
|
||||||
|
transaction::Transaction, transaction::TransactionError, transport::TransportError,
|
||||||
|
},
|
||||||
|
spl_stake_pool::{error, id, instruction, state},
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn setup() -> (BanksClient, Keypair, Hash, StakePoolAccounts, Keypair) {
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
stake_pool_accounts
|
||||||
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let new_staker = Keypair::new();
|
||||||
|
|
||||||
|
(
|
||||||
|
banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
new_staker,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn success_set_staker_as_manager() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_staker) =
|
||||||
|
setup().await;
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[instruction::set_staker(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&stake_pool_accounts.manager.pubkey(),
|
||||||
|
&new_staker.pubkey(),
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer, &stake_pool_accounts.manager], recent_blockhash);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
|
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
|
||||||
|
let stake_pool = state::StakePool::try_from_slice(&stake_pool.data.as_slice()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(stake_pool.staker, new_staker.pubkey());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn success_set_staker_as_staker() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_staker) =
|
||||||
|
setup().await;
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[instruction::set_staker(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
&new_staker.pubkey(),
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer, &stake_pool_accounts.staker], recent_blockhash);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
|
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
|
||||||
|
let stake_pool = state::StakePool::try_from_slice(&stake_pool.data.as_slice()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(stake_pool.staker, new_staker.pubkey());
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[instruction::set_staker(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&new_staker.pubkey(),
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer, &new_staker], recent_blockhash);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
|
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
|
||||||
|
let stake_pool = state::StakePool::try_from_slice(&stake_pool.data.as_slice()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(stake_pool.staker, stake_pool_accounts.staker.pubkey());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_wrong_manager() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_staker) =
|
||||||
|
setup().await;
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[instruction::set_staker(
|
||||||
|
&id(),
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
&new_staker.pubkey(),
|
||||||
|
&new_staker.pubkey(),
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer, &new_staker], recent_blockhash);
|
||||||
|
let transaction_error = banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match transaction_error {
|
||||||
|
TransportError::TransactionError(TransactionError::InstructionError(
|
||||||
|
_,
|
||||||
|
InstructionError::Custom(error_index),
|
||||||
|
)) => {
|
||||||
|
let program_error = error::StakePoolError::SignatureMissing as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error occurs while malicious try to set manager"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_set_staker_without_signature() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, new_staker) =
|
||||||
|
setup().await;
|
||||||
|
|
||||||
|
let data = instruction::StakePoolInstruction::SetStaker
|
||||||
|
.try_to_vec()
|
||||||
|
.unwrap();
|
||||||
|
let accounts = vec![
|
||||||
|
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
|
||||||
|
AccountMeta::new_readonly(stake_pool_accounts.manager.pubkey(), false),
|
||||||
|
AccountMeta::new_readonly(new_staker.pubkey(), false),
|
||||||
|
];
|
||||||
|
let instruction = Instruction {
|
||||||
|
program_id: id(),
|
||||||
|
accounts,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
let transaction_error = banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match transaction_error {
|
||||||
|
TransportError::TransactionError(TransactionError::InstructionError(
|
||||||
|
_,
|
||||||
|
InstructionError::Custom(error_index),
|
||||||
|
)) => {
|
||||||
|
let program_error = error::StakePoolError::SignatureMissing as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error occurs while try to set new manager without signature"),
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,60 +3,219 @@
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
|
borsh::BorshDeserialize,
|
||||||
helpers::*,
|
helpers::*,
|
||||||
|
solana_program::{instruction::InstructionError, pubkey::Pubkey},
|
||||||
solana_program_test::*,
|
solana_program_test::*,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
instruction::InstructionError, signature::Keypair, signature::Signer,
|
signature::{Keypair, Signer},
|
||||||
transaction::Transaction, transaction::TransactionError, transport::TransportError,
|
transaction::TransactionError,
|
||||||
},
|
},
|
||||||
spl_stake_pool::*,
|
spl_stake_pool::{error::StakePoolError, state::StakePool},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::test]
|
async fn setup() -> (
|
||||||
async fn test_update_stake_pool_balance() {
|
ProgramTestContext,
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
StakePoolAccounts,
|
||||||
|
Vec<ValidatorStakeAccount>,
|
||||||
|
) {
|
||||||
|
let mut context = program_test().start_with_context().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
1,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// TODO: Waiting for the ability to advance clock (or modify account data) to finish the tests
|
// Add several accounts
|
||||||
|
let mut stake_accounts: Vec<ValidatorStakeAccount> = vec![];
|
||||||
|
const STAKE_ACCOUNTS: u64 = 3;
|
||||||
|
for _ in 0..STAKE_ACCOUNTS {
|
||||||
|
let validator_stake_account = simple_add_validator_to_pool(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _deposit_info = simple_deposit(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
&validator_stake_account,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
stake_accounts.push(validator_stake_account);
|
||||||
|
}
|
||||||
|
|
||||||
|
(context, stake_pool_accounts, stake_accounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_update_stake_pool_balance_with_wrong_validator_list() {
|
async fn success() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut context, stake_pool_accounts, stake_accounts) = setup().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
|
||||||
stake_pool_accounts
|
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let wrong_stake_list_storage = Keypair::new();
|
let pre_balance = get_validator_list_sum(
|
||||||
let mut transaction = Transaction::new_with_payer(
|
&mut context.banks_client,
|
||||||
&[instruction::update_stake_pool_balance(
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
&id(),
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
)
|
||||||
&wrong_stake_list_storage.pubkey(),
|
.await;
|
||||||
|
let stake_pool = get_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let stake_pool = StakePool::try_from_slice(&stake_pool.data.as_slice()).unwrap();
|
||||||
|
assert_eq!(pre_balance, stake_pool.total_stake_lamports);
|
||||||
|
|
||||||
|
let pre_token_supply = get_token_supply(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.update_stake_pool_balance(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
)
|
)
|
||||||
.unwrap()],
|
.await;
|
||||||
Some(&payer.pubkey()),
|
assert!(error.is_none());
|
||||||
);
|
|
||||||
|
|
||||||
transaction.sign(&[&payer], recent_blockhash);
|
// Add extra funds, simulating rewards
|
||||||
let transaction_error = banks_client
|
const EXTRA_STAKE_AMOUNT: u64 = 1_000_000;
|
||||||
.process_transaction(transaction)
|
for stake_account in &stake_accounts {
|
||||||
|
transfer(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&stake_account.stake_account,
|
||||||
|
EXTRA_STAKE_AMOUNT,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update epoch
|
||||||
|
context.warp_to_slot(50_000).unwrap();
|
||||||
|
|
||||||
|
// Update list and pool
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.update_all(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
stake_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.vote.pubkey())
|
||||||
|
.collect::<Vec<Pubkey>>()
|
||||||
|
.as_slice(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
// Check fee
|
||||||
|
let post_balance = get_validator_list_sum(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let stake_pool = get_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let stake_pool = StakePool::try_from_slice(&stake_pool.data.as_slice()).unwrap();
|
||||||
|
assert_eq!(post_balance, stake_pool.total_stake_lamports);
|
||||||
|
|
||||||
|
let actual_fee = get_token_balance(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.pool_fee_account.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let pool_token_supply = get_token_supply(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let stake_pool_info = get_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let stake_pool = StakePool::try_from_slice(&stake_pool_info.data).unwrap();
|
||||||
|
let expected_fee = (post_balance - pre_balance) * pre_token_supply / pre_balance
|
||||||
|
* stake_pool.fee.numerator
|
||||||
|
/ stake_pool.fee.denominator;
|
||||||
|
assert_eq!(actual_fee, expected_fee);
|
||||||
|
assert_eq!(pool_token_supply, stake_pool.pool_token_supply);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_wrong_validator_list() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
|
let mut stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
stake_pool_accounts
|
||||||
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.err()
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
match transaction_error {
|
let wrong_validator_list = Keypair::new();
|
||||||
TransportError::TransactionError(TransactionError::InstructionError(
|
stake_pool_accounts.validator_list = wrong_validator_list;
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.update_stake_pool_balance(&mut banks_client, &payer, &recent_blockhash)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(
|
||||||
_,
|
_,
|
||||||
InstructionError::Custom(error_index),
|
InstructionError::Custom(error_index),
|
||||||
)) => {
|
) => {
|
||||||
let program_error = error::StakePoolError::InvalidValidatorStakeList as u32;
|
let program_error = StakePoolError::InvalidValidatorStakeList as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error occurs while try to update pool balance with wrong validator stake list account"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_wrong_pool_fee_account() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
|
let mut stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
stake_pool_accounts
|
||||||
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let wrong_fee_account = Keypair::new();
|
||||||
|
stake_pool_accounts.pool_fee_account = wrong_fee_account;
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.update_stake_pool_balance(&mut banks_client, &payer, &recent_blockhash)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(
|
||||||
|
_,
|
||||||
|
InstructionError::Custom(error_index),
|
||||||
|
) => {
|
||||||
|
let program_error = StakePoolError::InvalidFeeAccount as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong error occurs while try to update pool balance with wrong validator stake list account"),
|
_ => panic!("Wrong error occurs while try to update pool balance with wrong validator stake list account"),
|
||||||
|
|
|
@ -3,87 +3,607 @@
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::helpers::TEST_STAKE_AMOUNT,
|
borsh::BorshDeserialize,
|
||||||
helpers::*,
|
helpers::*,
|
||||||
solana_program::{native_token, pubkey::Pubkey},
|
solana_program::pubkey::Pubkey,
|
||||||
solana_program_test::*,
|
solana_program_test::*,
|
||||||
solana_sdk::signature::Signer,
|
solana_sdk::signature::{Keypair, Signer},
|
||||||
spl_stake_pool::{borsh::try_from_slice_unchecked, stake_program, state},
|
spl_stake_pool::{
|
||||||
|
borsh::try_from_slice_unchecked,
|
||||||
|
stake_program,
|
||||||
|
state::{StakePool, StakeStatus, ValidatorList},
|
||||||
|
MAX_VALIDATORS_TO_UPDATE, MINIMUM_ACTIVE_STAKE,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async fn get_list_sum(banks_client: &mut BanksClient, validator_list_key: &Pubkey) -> u64 {
|
async fn setup(
|
||||||
let validator_list = banks_client
|
num_validators: usize,
|
||||||
.get_account(*validator_list_key)
|
) -> (
|
||||||
.await
|
ProgramTestContext,
|
||||||
.expect("get_account")
|
StakePoolAccounts,
|
||||||
.expect("validator stake list not none");
|
Vec<ValidatorStakeAccount>,
|
||||||
let validator_list =
|
u64,
|
||||||
try_from_slice_unchecked::<state::ValidatorList>(validator_list.data.as_slice()).unwrap();
|
u64,
|
||||||
|
u64,
|
||||||
|
) {
|
||||||
|
let mut context = program_test().start_with_context().await;
|
||||||
|
let first_normal_slot = context.genesis_config().epoch_schedule.first_normal_slot;
|
||||||
|
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
|
||||||
|
let mut slot = first_normal_slot;
|
||||||
|
context.warp_to_slot(slot).unwrap();
|
||||||
|
|
||||||
validator_list
|
let reserve_stake_amount = TEST_STAKE_AMOUNT * num_validators as u64;
|
||||||
.validators
|
|
||||||
.iter()
|
|
||||||
.map(|info| info.balance)
|
|
||||||
.sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_update_validator_list_balance() {
|
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
reserve_stake_amount + 1,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Add several accounts
|
// so warmups / cooldowns go faster
|
||||||
let mut stake_accounts: Vec<ValidatorStakeAccount> = vec![];
|
let validator = Keypair::new();
|
||||||
const STAKE_ACCOUNTS: u64 = 3;
|
let vote = Keypair::new();
|
||||||
for _ in 0..STAKE_ACCOUNTS {
|
create_vote(
|
||||||
stake_accounts.push(
|
&mut context.banks_client,
|
||||||
simple_add_validator_to_pool(
|
&context.payer,
|
||||||
&mut banks_client,
|
&context.last_blockhash,
|
||||||
&payer,
|
&validator,
|
||||||
&recent_blockhash,
|
&vote,
|
||||||
&stake_pool_accounts,
|
)
|
||||||
)
|
.await;
|
||||||
.await,
|
let deposit_account =
|
||||||
);
|
DepositStakeAccount::new_with_vote(vote.pubkey(), validator.pubkey(), 100_000_000_000);
|
||||||
}
|
deposit_account
|
||||||
|
.create_and_delegate(
|
||||||
// Add stake extra funds
|
&mut context.banks_client,
|
||||||
const EXTRA_STAKE: u64 = 1_000_000;
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
for stake_account in stake_accounts {
|
|
||||||
transfer(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&stake_account.stake_account,
|
|
||||||
EXTRA_STAKE,
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
// Add several accounts with some stake
|
||||||
|
let mut stake_accounts: Vec<ValidatorStakeAccount> = vec![];
|
||||||
|
let mut deposit_accounts: Vec<DepositStakeAccount> = vec![];
|
||||||
|
for _ in 0..num_validators {
|
||||||
|
let stake_account = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey());
|
||||||
|
stake_account
|
||||||
|
.create_and_delegate(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&stake_pool_accounts.staker,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let deposit_account = DepositStakeAccount::new_with_vote(
|
||||||
|
stake_account.vote.pubkey(),
|
||||||
|
stake_account.stake_account,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
|
);
|
||||||
|
deposit_account
|
||||||
|
.create_and_delegate(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
stake_accounts.push(stake_account);
|
||||||
|
deposit_accounts.push(deposit_account);
|
||||||
}
|
}
|
||||||
|
|
||||||
let rent = banks_client.get_rent().await.unwrap();
|
// Warp forward so the stakes properly activate, and deposit
|
||||||
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>())
|
slot += 2 * slots_per_epoch;
|
||||||
+ native_token::sol_to_lamports(1.0);
|
context.warp_to_slot(slot).unwrap();
|
||||||
|
|
||||||
// Check current balance in the list
|
stake_pool_accounts
|
||||||
assert_eq!(
|
.update_all(
|
||||||
get_list_sum(
|
&mut context.banks_client,
|
||||||
&mut banks_client,
|
&context.payer,
|
||||||
&stake_pool_accounts.validator_list.pubkey()
|
&context.last_blockhash,
|
||||||
|
stake_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.vote.pubkey())
|
||||||
|
.collect::<Vec<Pubkey>>()
|
||||||
|
.as_slice(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.await,
|
.await;
|
||||||
STAKE_ACCOUNTS * (stake_rent + TEST_STAKE_AMOUNT)
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: Execute update list with updated clock
|
for stake_account in &stake_accounts {
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.add_validator_to_pool(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&stake_account.stake_account,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
for deposit_account in &deposit_accounts {
|
||||||
|
deposit_account
|
||||||
|
.deposit(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
slot += slots_per_epoch;
|
||||||
|
context.warp_to_slot(slot).unwrap();
|
||||||
|
|
||||||
|
stake_pool_accounts
|
||||||
|
.update_all(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
stake_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.vote.pubkey())
|
||||||
|
.collect::<Vec<Pubkey>>()
|
||||||
|
.as_slice(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
(
|
||||||
|
context,
|
||||||
|
stake_pool_accounts,
|
||||||
|
stake_accounts,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
|
reserve_stake_amount,
|
||||||
|
slot,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_update_validator_list_balance_with_uninitialized_validator_list() {} // TODO
|
async fn success() {
|
||||||
|
let num_validators = 5;
|
||||||
|
let (
|
||||||
|
mut context,
|
||||||
|
stake_pool_accounts,
|
||||||
|
stake_accounts,
|
||||||
|
validator_lamports,
|
||||||
|
reserve_lamports,
|
||||||
|
mut slot,
|
||||||
|
) = setup(num_validators).await;
|
||||||
|
|
||||||
|
// Check current balance in the list
|
||||||
|
let rent = context.banks_client.get_rent().await.unwrap();
|
||||||
|
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
|
||||||
|
// initially, have all of the deposits plus their rent, and the reserve stake
|
||||||
|
let initial_lamports =
|
||||||
|
(validator_lamports + stake_rent) * num_validators as u64 + reserve_lamports;
|
||||||
|
assert_eq!(
|
||||||
|
get_validator_list_sum(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.validator_list.pubkey()
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
initial_lamports,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simulate rewards
|
||||||
|
for stake_account in &stake_accounts {
|
||||||
|
context.increment_vote_account_credits(&stake_account.vote.pubkey(), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warp one more epoch so the rewards are paid out
|
||||||
|
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
|
||||||
|
slot += slots_per_epoch;
|
||||||
|
context.warp_to_slot(slot).unwrap();
|
||||||
|
|
||||||
|
stake_pool_accounts
|
||||||
|
.update_all(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
stake_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.vote.pubkey())
|
||||||
|
.collect::<Vec<Pubkey>>()
|
||||||
|
.as_slice(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let new_lamports = get_validator_list_sum(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(new_lamports > initial_lamports);
|
||||||
|
|
||||||
|
let stake_pool_info = get_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let stake_pool = StakePool::try_from_slice(&stake_pool_info.data).unwrap();
|
||||||
|
assert_eq!(new_lamports, stake_pool.total_stake_lamports);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_update_validator_list_balance_with_wrong_stake_state() {} // TODO
|
async fn merge_into_reserve() {
|
||||||
|
let (mut context, stake_pool_accounts, stake_accounts, lamports, _, mut slot) =
|
||||||
|
setup(MAX_VALIDATORS_TO_UPDATE).await;
|
||||||
|
|
||||||
|
let pre_lamports = get_validator_list_sum(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let reserve_stake = context
|
||||||
|
.banks_client
|
||||||
|
.get_account(stake_pool_accounts.reserve_stake.pubkey())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let pre_reserve_lamports = reserve_stake.lamports;
|
||||||
|
|
||||||
|
// Decrease from all validators
|
||||||
|
for stake_account in &stake_accounts {
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.decrease_validator_stake(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&stake_account.stake_account,
|
||||||
|
&stake_account.transient_stake_account,
|
||||||
|
lamports,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update, should not change, no merges yet
|
||||||
|
stake_pool_accounts
|
||||||
|
.update_all(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
stake_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.vote.pubkey())
|
||||||
|
.collect::<Vec<Pubkey>>()
|
||||||
|
.as_slice(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let expected_lamports = get_validator_list_sum(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(pre_lamports, expected_lamports);
|
||||||
|
|
||||||
|
let stake_pool_info = get_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let stake_pool = StakePool::try_from_slice(&stake_pool_info.data).unwrap();
|
||||||
|
assert_eq!(expected_lamports, stake_pool.total_stake_lamports);
|
||||||
|
|
||||||
|
// Warp one more epoch so the stakes deactivate
|
||||||
|
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
|
||||||
|
slot += slots_per_epoch;
|
||||||
|
context.warp_to_slot(slot).unwrap();
|
||||||
|
|
||||||
|
stake_pool_accounts
|
||||||
|
.update_all(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
stake_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.vote.pubkey())
|
||||||
|
.collect::<Vec<Pubkey>>()
|
||||||
|
.as_slice(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let expected_lamports = get_validator_list_sum(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(pre_lamports, expected_lamports);
|
||||||
|
|
||||||
|
let reserve_stake = context
|
||||||
|
.banks_client
|
||||||
|
.get_account(stake_pool_accounts.reserve_stake.pubkey())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let post_reserve_lamports = reserve_stake.lamports;
|
||||||
|
assert!(post_reserve_lamports > pre_reserve_lamports);
|
||||||
|
|
||||||
|
let stake_pool_info = get_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let stake_pool = StakePool::try_from_slice(&stake_pool_info.data).unwrap();
|
||||||
|
assert_eq!(expected_lamports, stake_pool.total_stake_lamports);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn merge_into_validator_stake() {
|
||||||
|
let (mut context, stake_pool_accounts, stake_accounts, lamports, reserve_lamports, mut slot) =
|
||||||
|
setup(MAX_VALIDATORS_TO_UPDATE).await;
|
||||||
|
|
||||||
|
let rent = context.banks_client.get_rent().await.unwrap();
|
||||||
|
let pre_lamports = get_validator_list_sum(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Increase stake to all validators
|
||||||
|
for stake_account in &stake_accounts {
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.increase_validator_stake(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&stake_account.transient_stake_account,
|
||||||
|
&stake_account.vote.pubkey(),
|
||||||
|
reserve_lamports / stake_accounts.len() as u64,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warp just a little bit to get a new blockhash and update again
|
||||||
|
context.warp_to_slot(slot + 10).unwrap();
|
||||||
|
|
||||||
|
// Update, should not change, no merges yet
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.update_all(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
stake_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.vote.pubkey())
|
||||||
|
.collect::<Vec<Pubkey>>()
|
||||||
|
.as_slice(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
let expected_lamports = get_validator_list_sum(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(pre_lamports, expected_lamports);
|
||||||
|
let stake_pool_info = get_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let stake_pool = StakePool::try_from_slice(&stake_pool_info.data).unwrap();
|
||||||
|
assert_eq!(expected_lamports, stake_pool.total_stake_lamports);
|
||||||
|
|
||||||
|
let stake_pool_info = get_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let stake_pool = StakePool::try_from_slice(&stake_pool_info.data).unwrap();
|
||||||
|
assert_eq!(expected_lamports, stake_pool.total_stake_lamports);
|
||||||
|
|
||||||
|
// Warp one more epoch so the stakes activate, ready to merge
|
||||||
|
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
|
||||||
|
slot += slots_per_epoch;
|
||||||
|
context.warp_to_slot(slot).unwrap();
|
||||||
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.update_all(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
stake_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.vote.pubkey())
|
||||||
|
.collect::<Vec<Pubkey>>()
|
||||||
|
.as_slice(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
let current_lamports = get_validator_list_sum(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let stake_pool_info = get_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let stake_pool = StakePool::try_from_slice(&stake_pool_info.data).unwrap();
|
||||||
|
assert_eq!(current_lamports, stake_pool.total_stake_lamports);
|
||||||
|
|
||||||
|
// Check that transient accounts are gone
|
||||||
|
for stake_account in &stake_accounts {
|
||||||
|
assert!(context
|
||||||
|
.banks_client
|
||||||
|
.get_account(stake_account.transient_stake_account)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check validator stake accounts have the expected balance now:
|
||||||
|
// validator stake account minimum + deposited lamports + 2 rents + increased lamports
|
||||||
|
let expected_lamports = MINIMUM_ACTIVE_STAKE
|
||||||
|
+ lamports
|
||||||
|
+ reserve_lamports / stake_accounts.len() as u64
|
||||||
|
+ 2 * rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
|
||||||
|
for stake_account in &stake_accounts {
|
||||||
|
let validator_stake =
|
||||||
|
get_account(&mut context.banks_client, &stake_account.stake_account).await;
|
||||||
|
assert_eq!(validator_stake.lamports, expected_lamports);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn merge_transient_stake_after_remove() {
|
||||||
|
let (mut context, stake_pool_accounts, stake_accounts, lamports, reserve_lamports, mut slot) =
|
||||||
|
setup(1).await;
|
||||||
|
|
||||||
|
let rent = context.banks_client.get_rent().await.unwrap();
|
||||||
|
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
|
||||||
|
let deactivated_lamports = lamports + stake_rent;
|
||||||
|
let new_authority = Pubkey::new_unique();
|
||||||
|
// Decrease and remove all validators
|
||||||
|
for stake_account in &stake_accounts {
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.decrease_validator_stake(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&stake_account.stake_account,
|
||||||
|
&stake_account.transient_stake_account,
|
||||||
|
deactivated_lamports,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.remove_validator_from_pool(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&new_authority,
|
||||||
|
&stake_account.stake_account,
|
||||||
|
&stake_account.transient_stake_account,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warp forward to merge time
|
||||||
|
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
|
||||||
|
slot += slots_per_epoch;
|
||||||
|
context.warp_to_slot(slot).unwrap();
|
||||||
|
|
||||||
|
// Update without merge, status should be DeactivatingTransient
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.update_all(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
stake_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.vote.pubkey())
|
||||||
|
.collect::<Vec<Pubkey>>()
|
||||||
|
.as_slice(),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
let validator_list = get_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let validator_list =
|
||||||
|
try_from_slice_unchecked::<ValidatorList>(validator_list.data.as_slice()).unwrap();
|
||||||
|
assert_eq!(validator_list.validators.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
validator_list.validators[0].status,
|
||||||
|
StakeStatus::DeactivatingTransient
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
validator_list.validators[0].stake_lamports,
|
||||||
|
deactivated_lamports
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update with merge, status should be ReadyForRemoval and no lamports
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.update_validator_list_balance(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
stake_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.vote.pubkey())
|
||||||
|
.collect::<Vec<Pubkey>>()
|
||||||
|
.as_slice(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
let validator_list = get_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let validator_list =
|
||||||
|
try_from_slice_unchecked::<ValidatorList>(validator_list.data.as_slice()).unwrap();
|
||||||
|
assert_eq!(validator_list.validators.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
validator_list.validators[0].status,
|
||||||
|
StakeStatus::ReadyForRemoval
|
||||||
|
);
|
||||||
|
assert_eq!(validator_list.validators[0].stake_lamports, 0);
|
||||||
|
|
||||||
|
let reserve_stake = context
|
||||||
|
.banks_client
|
||||||
|
.get_account(stake_pool_accounts.reserve_stake.pubkey())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
reserve_stake.lamports,
|
||||||
|
reserve_lamports + deactivated_lamports + stake_rent + 1
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update stake pool balance, should be gone
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.update_stake_pool_balance(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
let validator_list = get_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let validator_list =
|
||||||
|
try_from_slice_unchecked::<ValidatorList>(validator_list.data.as_slice()).unwrap();
|
||||||
|
assert_eq!(validator_list.validators.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_uninitialized_validator_list() {} // TODO
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_wrong_stake_state() {} // TODO
|
||||||
|
|
|
@ -19,7 +19,8 @@ use {
|
||||||
transport::TransportError,
|
transport::TransportError,
|
||||||
},
|
},
|
||||||
spl_stake_pool::{
|
spl_stake_pool::{
|
||||||
borsh::try_from_slice_unchecked, error, id, instruction, stake_program, state,
|
borsh::try_from_slice_unchecked, error::StakePoolError, id, instruction, stake_program,
|
||||||
|
state,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,63 +30,37 @@ async fn setup() -> (
|
||||||
Hash,
|
Hash,
|
||||||
StakePoolAccounts,
|
StakePoolAccounts,
|
||||||
ValidatorStakeAccount,
|
ValidatorStakeAccount,
|
||||||
Keypair,
|
|
||||||
) {
|
) {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let user = Keypair::new();
|
let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey());
|
||||||
|
|
||||||
let user_stake = ValidatorStakeAccount::new_with_target_authority(
|
|
||||||
&stake_pool_accounts.deposit_authority,
|
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
|
||||||
);
|
|
||||||
user_stake
|
user_stake
|
||||||
.create_and_delegate(
|
.create_and_delegate(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&stake_pool_accounts.owner,
|
&stake_pool_accounts.staker,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// make pool token account
|
|
||||||
let user_pool_account = Keypair::new();
|
|
||||||
create_token_account(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_pool_account,
|
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
|
||||||
&user.pubkey(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
(
|
(
|
||||||
banks_client,
|
banks_client,
|
||||||
payer,
|
payer,
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
stake_pool_accounts,
|
stake_pool_accounts,
|
||||||
user_stake,
|
user_stake,
|
||||||
user_pool_account,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_add_validator_to_pool() {
|
async fn success() {
|
||||||
let (
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
|
||||||
mut banks_client,
|
setup().await;
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let error = stake_pool_accounts
|
let error = stake_pool_accounts
|
||||||
.add_validator_to_pool(
|
.add_validator_to_pool(
|
||||||
|
@ -93,28 +68,10 @@ async fn test_add_validator_to_pool() {
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake.stake_account,
|
&user_stake.stake_account,
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert!(error.is_none());
|
assert!(error.is_none());
|
||||||
|
|
||||||
let stake_account_balance = banks_client
|
|
||||||
.get_account(user_stake.stake_account)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
.lamports;
|
|
||||||
let deposit_tokens = stake_account_balance; // For now 1:1 math
|
|
||||||
// Check token account balance
|
|
||||||
let token_balance = get_token_balance(&mut banks_client, &user_pool_account.pubkey()).await;
|
|
||||||
assert_eq!(token_balance, deposit_tokens);
|
|
||||||
let pool_fee_token_balance = get_token_balance(
|
|
||||||
&mut banks_client,
|
|
||||||
&stake_pool_accounts.pool_fee_account.pubkey(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert_eq!(pool_fee_token_balance, 0); // No fee when adding validator stake accounts
|
|
||||||
|
|
||||||
// Check if validator account was added to the list
|
// Check if validator account was added to the list
|
||||||
let validator_list = get_account(
|
let validator_list = get_account(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
|
@ -129,9 +86,10 @@ async fn test_add_validator_to_pool() {
|
||||||
account_type: state::AccountType::ValidatorList,
|
account_type: state::AccountType::ValidatorList,
|
||||||
max_validators: stake_pool_accounts.max_validators,
|
max_validators: stake_pool_accounts.max_validators,
|
||||||
validators: vec![state::ValidatorStakeInfo {
|
validators: vec![state::ValidatorStakeInfo {
|
||||||
vote_account: user_stake.vote.pubkey(),
|
status: state::StakeStatus::Active,
|
||||||
|
vote_account_address: user_stake.vote.pubkey(),
|
||||||
last_update_epoch: 0,
|
last_update_epoch: 0,
|
||||||
balance: stake_account_balance,
|
stake_lamports: 0,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -155,105 +113,9 @@ async fn test_add_validator_to_pool() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_add_validator_to_pool_with_wrong_token_program_id() {
|
async fn fail_with_wrong_validator_list_account() {
|
||||||
let (
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
|
||||||
mut banks_client,
|
setup().await;
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(
|
|
||||||
&[instruction::add_validator_to_pool(
|
|
||||||
&id(),
|
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
|
||||||
&stake_pool_accounts.deposit_authority,
|
|
||||||
&stake_pool_accounts.withdraw_authority,
|
|
||||||
&stake_pool_accounts.validator_list.pubkey(),
|
|
||||||
&user_stake.stake_account,
|
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
|
||||||
&stake_program::id(),
|
|
||||||
)
|
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
|
||||||
);
|
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
|
||||||
let transaction_error = banks_client
|
|
||||||
.process_transaction(transaction)
|
|
||||||
.await
|
|
||||||
.err()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
match transaction_error {
|
|
||||||
TransportError::TransactionError(TransactionError::InstructionError(_, error)) => {
|
|
||||||
assert_eq!(error, InstructionError::IncorrectProgramId);
|
|
||||||
}
|
|
||||||
_ => panic!("Wrong error occurs while try to add validator stake address with wrong token program ID"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_add_validator_to_pool_with_wrong_pool_mint_account() {
|
|
||||||
let (
|
|
||||||
mut banks_client,
|
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let wrong_pool_mint = Keypair::new();
|
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(
|
|
||||||
&[instruction::add_validator_to_pool(
|
|
||||||
&id(),
|
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
|
||||||
&stake_pool_accounts.deposit_authority,
|
|
||||||
&stake_pool_accounts.withdraw_authority,
|
|
||||||
&stake_pool_accounts.validator_list.pubkey(),
|
|
||||||
&user_stake.stake_account,
|
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
&wrong_pool_mint.pubkey(),
|
|
||||||
&spl_token::id(),
|
|
||||||
)
|
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
|
||||||
);
|
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
|
||||||
let transaction_error = banks_client
|
|
||||||
.process_transaction(transaction)
|
|
||||||
.await
|
|
||||||
.err()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
match transaction_error {
|
|
||||||
TransportError::TransactionError(TransactionError::InstructionError(
|
|
||||||
_,
|
|
||||||
InstructionError::Custom(error_index),
|
|
||||||
)) => {
|
|
||||||
let program_error = error::StakePoolError::WrongPoolMint as u32;
|
|
||||||
assert_eq!(error_index, program_error);
|
|
||||||
}
|
|
||||||
_ => panic!("Wrong error occurs while try to add validator stake address with wrong pool mint account"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_add_validator_to_pool_with_wrong_validator_list_account() {
|
|
||||||
let (
|
|
||||||
mut banks_client,
|
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let wrong_validator_list = Keypair::new();
|
let wrong_validator_list = Keypair::new();
|
||||||
|
|
||||||
|
@ -261,19 +123,14 @@ async fn test_add_validator_to_pool_with_wrong_validator_list_account() {
|
||||||
&[instruction::add_validator_to_pool(
|
&[instruction::add_validator_to_pool(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
&stake_pool_accounts.deposit_authority,
|
|
||||||
&stake_pool_accounts.withdraw_authority,
|
&stake_pool_accounts.withdraw_authority,
|
||||||
&wrong_validator_list.pubkey(),
|
&wrong_validator_list.pubkey(),
|
||||||
&user_stake.stake_account,
|
&user_stake.stake_account,
|
||||||
&user_pool_account.pubkey(),
|
)],
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
|
||||||
&spl_token::id(),
|
|
||||||
)
|
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
transaction.sign(&[&payer, &stake_pool_accounts.staker], recent_blockhash);
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
@ -285,7 +142,7 @@ async fn test_add_validator_to_pool_with_wrong_validator_list_account() {
|
||||||
_,
|
_,
|
||||||
InstructionError::Custom(error_index),
|
InstructionError::Custom(error_index),
|
||||||
)) => {
|
)) => {
|
||||||
let program_error = error::StakePoolError::InvalidValidatorStakeList as u32;
|
let program_error = StakePoolError::InvalidValidatorStakeList as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong error occurs while try to add validator stake address with wrong validator stake list account"),
|
_ => panic!("Wrong error occurs while try to add validator stake address with wrong validator stake list account"),
|
||||||
|
@ -293,15 +150,122 @@ async fn test_add_validator_to_pool_with_wrong_validator_list_account() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_try_to_add_already_added_validator_stake_account() {
|
async fn fail_too_little_stake() {
|
||||||
let (
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
mut banks_client,
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
payer,
|
stake_pool_accounts
|
||||||
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey());
|
||||||
|
create_vote(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake.validator,
|
||||||
|
&user_stake.vote,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
create_validator_stake_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake.stake_pool,
|
||||||
|
&stake_pool_accounts.staker,
|
||||||
|
&user_stake.stake_account,
|
||||||
|
&user_stake.vote.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Create stake account to withdraw to
|
||||||
|
let split = Keypair::new();
|
||||||
|
create_blank_stake_account(&mut banks_client, &payer, &recent_blockhash, &split).await;
|
||||||
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
|
&[stake_program::split_only(
|
||||||
|
&user_stake.stake_account,
|
||||||
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
|
1,
|
||||||
|
&split.pubkey(),
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer, &stake_pool_accounts.staker],
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
stake_pool_accounts,
|
);
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
) = setup().await;
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.add_validator_to_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake.stake_account,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
error,
|
||||||
|
TransactionError::InstructionError(
|
||||||
|
0,
|
||||||
|
InstructionError::Custom(StakePoolError::StakeLamportsNotEqualToMinimum as u32)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_too_much_stake() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
stake_pool_accounts
|
||||||
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey());
|
||||||
|
user_stake
|
||||||
|
.create_and_delegate(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts.staker,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
transfer(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake.stake_account,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.add_validator_to_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake.stake_account,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
error,
|
||||||
|
TransactionError::InstructionError(
|
||||||
|
0,
|
||||||
|
InstructionError::Custom(StakePoolError::StakeLamportsNotEqualToMinimum as u32)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_double_add() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
|
||||||
|
setup().await;
|
||||||
|
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.add_validator_to_pool(
|
.add_validator_to_pool(
|
||||||
|
@ -309,7 +273,6 @@ async fn test_try_to_add_already_added_validator_stake_account() {
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake.stake_account,
|
&user_stake.stake_account,
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -321,7 +284,6 @@ async fn test_try_to_add_already_added_validator_stake_account() {
|
||||||
&payer,
|
&payer,
|
||||||
&latest_blockhash,
|
&latest_blockhash,
|
||||||
&user_stake.stake_account,
|
&user_stake.stake_account,
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -331,7 +293,7 @@ async fn test_try_to_add_already_added_validator_stake_account() {
|
||||||
_,
|
_,
|
||||||
InstructionError::Custom(error_index),
|
InstructionError::Custom(error_index),
|
||||||
)) => {
|
)) => {
|
||||||
let program_error = error::StakePoolError::ValidatorAlreadyAdded as u32;
|
let program_error = StakePoolError::ValidatorAlreadyAdded as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong error occurs while try to add already added validator stake account"),
|
_ => panic!("Wrong error occurs while try to add already added validator stake account"),
|
||||||
|
@ -339,15 +301,9 @@ async fn test_try_to_add_already_added_validator_stake_account() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_not_owner_try_to_add_validator_to_pool() {
|
async fn fail_wrong_staker() {
|
||||||
let (
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
|
||||||
mut banks_client,
|
setup().await;
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let malicious = Keypair::new();
|
let malicious = Keypair::new();
|
||||||
|
|
||||||
|
@ -356,15 +312,10 @@ async fn test_not_owner_try_to_add_validator_to_pool() {
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&malicious.pubkey(),
|
&malicious.pubkey(),
|
||||||
&stake_pool_accounts.deposit_authority,
|
|
||||||
&stake_pool_accounts.withdraw_authority,
|
&stake_pool_accounts.withdraw_authority,
|
||||||
&stake_pool_accounts.validator_list.pubkey(),
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
&user_stake.stake_account,
|
&user_stake.stake_account,
|
||||||
&user_pool_account.pubkey(),
|
)],
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
|
||||||
&spl_token::id(),
|
|
||||||
)
|
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
transaction.sign(&[&payer, &malicious], recent_blockhash);
|
transaction.sign(&[&payer, &malicious], recent_blockhash);
|
||||||
|
@ -379,7 +330,7 @@ async fn test_not_owner_try_to_add_validator_to_pool() {
|
||||||
_,
|
_,
|
||||||
InstructionError::Custom(error_index),
|
InstructionError::Custom(error_index),
|
||||||
)) => {
|
)) => {
|
||||||
let program_error = error::StakePoolError::WrongOwner as u32;
|
let program_error = StakePoolError::WrongStaker as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong error occurs while malicious try to add validator stake account"),
|
_ => panic!("Wrong error occurs while malicious try to add validator stake account"),
|
||||||
|
@ -387,28 +338,18 @@ async fn test_not_owner_try_to_add_validator_to_pool() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_not_owner_try_to_add_validator_to_pool_without_signature() {
|
async fn fail_without_signature() {
|
||||||
let (
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
|
||||||
mut banks_client,
|
setup().await;
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let accounts = vec![
|
let accounts = vec![
|
||||||
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
|
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.owner.pubkey(), false),
|
AccountMeta::new_readonly(stake_pool_accounts.staker.pubkey(), false),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.deposit_authority, false),
|
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
|
AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
|
||||||
AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
|
AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
|
||||||
AccountMeta::new(user_stake.stake_account, false),
|
AccountMeta::new(user_stake.stake_account, false),
|
||||||
AccountMeta::new(user_pool_account.pubkey(), false),
|
|
||||||
AccountMeta::new(stake_pool_accounts.pool_mint.pubkey(), false),
|
|
||||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
|
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
|
||||||
AccountMeta::new_readonly(spl_token::id(), false),
|
|
||||||
AccountMeta::new_readonly(stake_program::id(), false),
|
AccountMeta::new_readonly(stake_program::id(), false),
|
||||||
];
|
];
|
||||||
let instruction = Instruction {
|
let instruction = Instruction {
|
||||||
|
@ -432,7 +373,7 @@ async fn test_not_owner_try_to_add_validator_to_pool_without_signature() {
|
||||||
_,
|
_,
|
||||||
InstructionError::Custom(error_index),
|
InstructionError::Custom(error_index),
|
||||||
)) => {
|
)) => {
|
||||||
let program_error = error::StakePoolError::SignatureMissing as u32;
|
let program_error = StakePoolError::SignatureMissing as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong error occurs while malicious try to add validator stake account without signing transaction"),
|
_ => panic!("Wrong error occurs while malicious try to add validator stake account without signing transaction"),
|
||||||
|
@ -440,30 +381,20 @@ async fn test_not_owner_try_to_add_validator_to_pool_without_signature() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_add_validator_to_pool_with_wrong_stake_program_id() {
|
async fn fail_with_wrong_stake_program_id() {
|
||||||
let (
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) =
|
||||||
mut banks_client,
|
setup().await;
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let wrong_stake_program = Pubkey::new_unique();
|
let wrong_stake_program = Pubkey::new_unique();
|
||||||
|
|
||||||
let accounts = vec![
|
let accounts = vec![
|
||||||
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
|
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.owner.pubkey(), true),
|
AccountMeta::new_readonly(stake_pool_accounts.staker.pubkey(), true),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.deposit_authority, false),
|
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
|
AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
|
||||||
AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
|
AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
|
||||||
AccountMeta::new(user_stake.stake_account, false),
|
AccountMeta::new(user_stake.stake_account, false),
|
||||||
AccountMeta::new(user_pool_account.pubkey(), false),
|
|
||||||
AccountMeta::new(stake_pool_accounts.pool_mint.pubkey(), false),
|
|
||||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
|
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
|
||||||
AccountMeta::new_readonly(spl_token::id(), false),
|
|
||||||
AccountMeta::new_readonly(wrong_stake_program, false),
|
AccountMeta::new_readonly(wrong_stake_program, false),
|
||||||
];
|
];
|
||||||
let instruction = Instruction {
|
let instruction = Instruction {
|
||||||
|
@ -474,7 +405,7 @@ async fn test_add_validator_to_pool_with_wrong_stake_program_id() {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
};
|
};
|
||||||
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
transaction.sign(&[&payer, &stake_pool_accounts.staker], recent_blockhash);
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
@ -492,64 +423,42 @@ async fn test_add_validator_to_pool_with_wrong_stake_program_id() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_add_too_many_validator_stake_accounts() {
|
async fn fail_add_too_many_validator_stake_accounts() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let mut stake_pool_accounts = StakePoolAccounts::new();
|
let mut stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts.max_validators = 1;
|
stake_pool_accounts.max_validators = 1;
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let user = Keypair::new();
|
let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey());
|
||||||
|
|
||||||
let user_stake = ValidatorStakeAccount::new_with_target_authority(
|
|
||||||
&stake_pool_accounts.deposit_authority,
|
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
|
||||||
);
|
|
||||||
user_stake
|
user_stake
|
||||||
.create_and_delegate(
|
.create_and_delegate(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&stake_pool_accounts.owner,
|
&stake_pool_accounts.staker,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// make pool token account
|
|
||||||
let user_pool_account = Keypair::new();
|
|
||||||
create_token_account(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_pool_account,
|
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
|
||||||
&user.pubkey(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let error = stake_pool_accounts
|
let error = stake_pool_accounts
|
||||||
.add_validator_to_pool(
|
.add_validator_to_pool(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake.stake_account,
|
&user_stake.stake_account,
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert!(error.is_none());
|
assert!(error.is_none());
|
||||||
|
|
||||||
let user_stake = ValidatorStakeAccount::new_with_target_authority(
|
let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey());
|
||||||
&stake_pool_accounts.deposit_authority,
|
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
|
||||||
);
|
|
||||||
user_stake
|
user_stake
|
||||||
.create_and_delegate(
|
.create_and_delegate(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&stake_pool_accounts.owner,
|
&stake_pool_accounts.staker,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let error = stake_pool_accounts
|
let error = stake_pool_accounts
|
||||||
|
@ -558,7 +467,6 @@ async fn test_add_too_many_validator_stake_accounts() {
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake.stake_account,
|
&user_stake.stake_account,
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -570,7 +478,7 @@ async fn test_add_too_many_validator_stake_accounts() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_add_validator_to_pool_to_unupdated_stake_pool() {} // TODO
|
async fn fail_with_unupdated_stake_pool() {} // TODO
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_add_validator_to_pool_with_uninitialized_validator_list_account() {} // TODO
|
async fn fail_with_uninitialized_validator_list_account() {} // TODO
|
||||||
|
|
|
@ -26,16 +26,24 @@ async fn success_create_validator_stake_account() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let validator = Keypair::new();
|
let validator = Keypair::new();
|
||||||
create_vote(&mut banks_client, &payer, &recent_blockhash, &validator).await;
|
let vote = Keypair::new();
|
||||||
|
create_vote(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator,
|
||||||
|
&vote,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let (stake_account, _) = find_stake_program_address(
|
let (stake_account, _) = find_stake_program_address(
|
||||||
&id(),
|
&id(),
|
||||||
&validator.pubkey(),
|
&vote.pubkey(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -43,15 +51,14 @@ async fn success_create_validator_stake_account() {
|
||||||
&[instruction::create_validator_stake_account(
|
&[instruction::create_validator_stake_account(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
&payer.pubkey(),
|
&payer.pubkey(),
|
||||||
&stake_account,
|
&stake_account,
|
||||||
&validator.pubkey(),
|
&vote.pubkey(),
|
||||||
)
|
)],
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
transaction.sign(&[&payer, &stake_pool_accounts.staker], recent_blockhash);
|
||||||
banks_client.process_transaction(transaction).await.unwrap();
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
// Check authorities
|
// Check authorities
|
||||||
|
@ -59,12 +66,15 @@ async fn success_create_validator_stake_account() {
|
||||||
let stake_state = deserialize::<stake_program::StakeState>(&stake.data).unwrap();
|
let stake_state = deserialize::<stake_program::StakeState>(&stake.data).unwrap();
|
||||||
match stake_state {
|
match stake_state {
|
||||||
stake_program::StakeState::Stake(meta, stake) => {
|
stake_program::StakeState::Stake(meta, stake) => {
|
||||||
assert_eq!(&meta.authorized.staker, &stake_pool_accounts.owner.pubkey());
|
assert_eq!(
|
||||||
|
&meta.authorized.staker,
|
||||||
|
&stake_pool_accounts.staker.pubkey()
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&meta.authorized.withdrawer,
|
&meta.authorized.withdrawer,
|
||||||
&stake_pool_accounts.owner.pubkey()
|
&stake_pool_accounts.staker.pubkey()
|
||||||
);
|
);
|
||||||
assert_eq!(stake.delegation.voter_pubkey, validator.pubkey());
|
assert_eq!(stake.delegation.voter_pubkey, vote.pubkey());
|
||||||
}
|
}
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
}
|
}
|
||||||
|
@ -75,7 +85,7 @@ async fn fail_create_validator_stake_account_on_non_vote_account() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -88,15 +98,14 @@ async fn fail_create_validator_stake_account_on_non_vote_account() {
|
||||||
&[instruction::create_validator_stake_account(
|
&[instruction::create_validator_stake_account(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
&payer.pubkey(),
|
&payer.pubkey(),
|
||||||
&stake_account,
|
&stake_account,
|
||||||
&validator,
|
&validator,
|
||||||
)
|
)],
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
transaction.sign(&[&payer, &stake_pool_accounts.staker], recent_blockhash);
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
@ -115,7 +124,7 @@ async fn fail_create_validator_stake_account_with_wrong_system_program() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -126,7 +135,7 @@ async fn fail_create_validator_stake_account_with_wrong_system_program() {
|
||||||
let wrong_system_program = Pubkey::new_unique();
|
let wrong_system_program = Pubkey::new_unique();
|
||||||
let accounts = vec![
|
let accounts = vec![
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.stake_pool.pubkey(), false),
|
AccountMeta::new_readonly(stake_pool_accounts.stake_pool.pubkey(), false),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.owner.pubkey(), true),
|
AccountMeta::new_readonly(stake_pool_accounts.staker.pubkey(), true),
|
||||||
AccountMeta::new(payer.pubkey(), true),
|
AccountMeta::new(payer.pubkey(), true),
|
||||||
AccountMeta::new(stake_account, false),
|
AccountMeta::new(stake_account, false),
|
||||||
AccountMeta::new_readonly(validator, false),
|
AccountMeta::new_readonly(validator, false),
|
||||||
|
@ -146,7 +155,7 @@ async fn fail_create_validator_stake_account_with_wrong_system_program() {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
transaction.sign(&[&payer, &stake_pool_accounts.staker], recent_blockhash);
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
@ -165,7 +174,7 @@ async fn fail_create_validator_stake_account_with_wrong_stake_program() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -176,7 +185,7 @@ async fn fail_create_validator_stake_account_with_wrong_stake_program() {
|
||||||
let wrong_stake_program = Pubkey::new_unique();
|
let wrong_stake_program = Pubkey::new_unique();
|
||||||
let accounts = vec![
|
let accounts = vec![
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.stake_pool.pubkey(), false),
|
AccountMeta::new_readonly(stake_pool_accounts.stake_pool.pubkey(), false),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.owner.pubkey(), true),
|
AccountMeta::new_readonly(stake_pool_accounts.staker.pubkey(), true),
|
||||||
AccountMeta::new(payer.pubkey(), true),
|
AccountMeta::new(payer.pubkey(), true),
|
||||||
AccountMeta::new(stake_account, false),
|
AccountMeta::new(stake_account, false),
|
||||||
AccountMeta::new_readonly(validator, false),
|
AccountMeta::new_readonly(validator, false),
|
||||||
|
@ -196,7 +205,7 @@ async fn fail_create_validator_stake_account_with_wrong_stake_program() {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
transaction.sign(&[&payer, &stake_pool_accounts.staker], recent_blockhash);
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
@ -215,7 +224,7 @@ async fn fail_create_validator_stake_account_with_incorrect_address() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -226,15 +235,14 @@ async fn fail_create_validator_stake_account_with_incorrect_address() {
|
||||||
&[instruction::create_validator_stake_account(
|
&[instruction::create_validator_stake_account(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
&payer.pubkey(),
|
&payer.pubkey(),
|
||||||
&stake_account.pubkey(),
|
&stake_account.pubkey(),
|
||||||
&validator,
|
&validator,
|
||||||
)
|
)],
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
transaction.sign(&[&payer, &stake_pool_accounts.staker], recent_blockhash);
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -19,7 +19,8 @@ use {
|
||||||
transport::TransportError,
|
transport::TransportError,
|
||||||
},
|
},
|
||||||
spl_stake_pool::{
|
spl_stake_pool::{
|
||||||
borsh::try_from_slice_unchecked, error, id, instruction, stake_program, state,
|
borsh::try_from_slice_unchecked, error::StakePoolError, id, instruction, stake_program,
|
||||||
|
state,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,51 +30,30 @@ async fn setup() -> (
|
||||||
Hash,
|
Hash,
|
||||||
StakePoolAccounts,
|
StakePoolAccounts,
|
||||||
ValidatorStakeAccount,
|
ValidatorStakeAccount,
|
||||||
Keypair,
|
|
||||||
Keypair,
|
|
||||||
) {
|
) {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 10_000_000_000)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let user = Keypair::new();
|
let validator_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey());
|
||||||
|
validator_stake
|
||||||
let user_stake = ValidatorStakeAccount::new_with_target_authority(
|
|
||||||
&stake_pool_accounts.deposit_authority,
|
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
|
||||||
);
|
|
||||||
user_stake
|
|
||||||
.create_and_delegate(
|
.create_and_delegate(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&stake_pool_accounts.owner,
|
&stake_pool_accounts.staker,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// make pool token account
|
|
||||||
let user_pool_account = Keypair::new();
|
|
||||||
create_token_account(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_pool_account,
|
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
|
||||||
&user.pubkey(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let error = stake_pool_accounts
|
let error = stake_pool_accounts
|
||||||
.add_validator_to_pool(
|
.add_validator_to_pool(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake.stake_account,
|
&validator_stake.stake_account,
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert!(error.is_none());
|
assert!(error.is_none());
|
||||||
|
@ -83,35 +63,14 @@ async fn setup() -> (
|
||||||
payer,
|
payer,
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
stake_pool_accounts,
|
stake_pool_accounts,
|
||||||
user_stake,
|
validator_stake,
|
||||||
user_pool_account,
|
|
||||||
user,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_remove_validator_from_pool() {
|
async fn success() {
|
||||||
let (
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
|
||||||
mut banks_client,
|
setup().await;
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
user,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let tokens_to_burn = get_token_balance(&mut banks_client, &user_pool_account.pubkey()).await;
|
|
||||||
delegate_tokens(
|
|
||||||
&mut banks_client,
|
|
||||||
&payer,
|
|
||||||
&recent_blockhash,
|
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
&user,
|
|
||||||
&stake_pool_accounts.withdraw_authority,
|
|
||||||
tokens_to_burn,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let new_authority = Pubkey::new_unique();
|
let new_authority = Pubkey::new_unique();
|
||||||
let error = stake_pool_accounts
|
let error = stake_pool_accounts
|
||||||
|
@ -119,17 +78,13 @@ async fn test_remove_validator_from_pool() {
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake.stake_account,
|
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
&new_authority,
|
&new_authority,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert!(error.is_none());
|
assert!(error.is_none());
|
||||||
|
|
||||||
// Check if all tokens were burned
|
|
||||||
let tokens_left = get_token_balance(&mut banks_client, &user_pool_account.pubkey()).await;
|
|
||||||
assert_eq!(tokens_left, 0);
|
|
||||||
|
|
||||||
// Check if account was removed from the list of stake accounts
|
// Check if account was removed from the list of stake accounts
|
||||||
let validator_list = get_account(
|
let validator_list = get_account(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
|
@ -148,7 +103,7 @@ async fn test_remove_validator_from_pool() {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check of stake account authority has changed
|
// Check of stake account authority has changed
|
||||||
let stake = get_account(&mut banks_client, &user_stake.stake_account).await;
|
let stake = get_account(&mut banks_client, &validator_stake.stake_account).await;
|
||||||
let stake_state = deserialize::<stake_program::StakeState>(&stake.data).unwrap();
|
let stake_state = deserialize::<stake_program::StakeState>(&stake.data).unwrap();
|
||||||
match stake_state {
|
match stake_state {
|
||||||
stake_program::StakeState::Stake(meta, _) => {
|
stake_program::StakeState::Stake(meta, _) => {
|
||||||
|
@ -160,31 +115,22 @@ async fn test_remove_validator_from_pool() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_remove_validator_from_pool_with_wrong_stake_program_id() {
|
async fn fail_with_wrong_stake_program_id() {
|
||||||
let (
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
|
||||||
mut banks_client,
|
setup().await;
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
_,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let wrong_stake_program = Pubkey::new_unique();
|
let wrong_stake_program = Pubkey::new_unique();
|
||||||
|
|
||||||
let new_authority = Pubkey::new_unique();
|
let new_authority = Pubkey::new_unique();
|
||||||
let accounts = vec![
|
let accounts = vec![
|
||||||
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
|
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.owner.pubkey(), true),
|
AccountMeta::new_readonly(stake_pool_accounts.staker.pubkey(), true),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
|
AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
|
||||||
AccountMeta::new_readonly(new_authority, false),
|
AccountMeta::new_readonly(new_authority, false),
|
||||||
AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
|
AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
|
||||||
AccountMeta::new(user_stake.stake_account, false),
|
AccountMeta::new(validator_stake.stake_account, false),
|
||||||
AccountMeta::new(user_pool_account.pubkey(), false),
|
AccountMeta::new_readonly(validator_stake.transient_stake_account, false),
|
||||||
AccountMeta::new(stake_pool_accounts.pool_mint.pubkey(), false),
|
|
||||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
AccountMeta::new_readonly(spl_token::id(), false),
|
|
||||||
AccountMeta::new_readonly(wrong_stake_program, false),
|
AccountMeta::new_readonly(wrong_stake_program, false),
|
||||||
];
|
];
|
||||||
let instruction = Instruction {
|
let instruction = Instruction {
|
||||||
|
@ -196,7 +142,7 @@ async fn test_remove_validator_from_pool_with_wrong_stake_program_id() {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
transaction.sign(&[&payer, &stake_pool_accounts.staker], recent_blockhash);
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
@ -215,112 +161,9 @@ async fn test_remove_validator_from_pool_with_wrong_stake_program_id() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_remove_validator_from_pool_with_wrong_token_program_id() {
|
async fn fail_with_wrong_validator_list_account() {
|
||||||
let (
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
|
||||||
mut banks_client,
|
setup().await;
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
_,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let wrong_token_program = Keypair::new();
|
|
||||||
|
|
||||||
let new_authority = Pubkey::new_unique();
|
|
||||||
let mut transaction = Transaction::new_with_payer(
|
|
||||||
&[instruction::remove_validator_from_pool(
|
|
||||||
&id(),
|
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
|
||||||
&stake_pool_accounts.withdraw_authority,
|
|
||||||
&new_authority,
|
|
||||||
&stake_pool_accounts.validator_list.pubkey(),
|
|
||||||
&user_stake.stake_account,
|
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
|
||||||
&wrong_token_program.pubkey(),
|
|
||||||
)
|
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
|
||||||
);
|
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
|
||||||
let transaction_error = banks_client
|
|
||||||
.process_transaction(transaction)
|
|
||||||
.await
|
|
||||||
.err()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
match transaction_error {
|
|
||||||
TransportError::TransactionError(TransactionError::InstructionError(_, error)) => {
|
|
||||||
assert_eq!(error, InstructionError::IncorrectProgramId);
|
|
||||||
}
|
|
||||||
_ => panic!("Wrong error occurs while try to remove validator stake address with wrong token program ID"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_remove_validator_from_pool_with_wrong_pool_mint_account() {
|
|
||||||
let (
|
|
||||||
mut banks_client,
|
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
_,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let wrong_pool_mint = Keypair::new();
|
|
||||||
|
|
||||||
let new_authority = Pubkey::new_unique();
|
|
||||||
let mut transaction = Transaction::new_with_payer(
|
|
||||||
&[instruction::remove_validator_from_pool(
|
|
||||||
&id(),
|
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
|
||||||
&stake_pool_accounts.withdraw_authority,
|
|
||||||
&new_authority,
|
|
||||||
&stake_pool_accounts.validator_list.pubkey(),
|
|
||||||
&user_stake.stake_account,
|
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
&wrong_pool_mint.pubkey(),
|
|
||||||
&spl_token::id(),
|
|
||||||
)
|
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
|
||||||
);
|
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
|
||||||
let transaction_error = banks_client
|
|
||||||
.process_transaction(transaction)
|
|
||||||
.await
|
|
||||||
.err()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
match transaction_error {
|
|
||||||
TransportError::TransactionError(TransactionError::InstructionError(
|
|
||||||
_,
|
|
||||||
InstructionError::Custom(error_index),
|
|
||||||
)) => {
|
|
||||||
let program_error = error::StakePoolError::WrongPoolMint as u32;
|
|
||||||
assert_eq!(error_index, program_error);
|
|
||||||
}
|
|
||||||
_ => panic!("Wrong error occurs while try to remove validator stake address with wrong pool mint account"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_remove_validator_from_pool_with_wrong_validator_list_account() {
|
|
||||||
let (
|
|
||||||
mut banks_client,
|
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
_,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let wrong_validator_list = Keypair::new();
|
let wrong_validator_list = Keypair::new();
|
||||||
|
|
||||||
|
@ -329,19 +172,16 @@ async fn test_remove_validator_from_pool_with_wrong_validator_list_account() {
|
||||||
&[instruction::remove_validator_from_pool(
|
&[instruction::remove_validator_from_pool(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
&stake_pool_accounts.owner.pubkey(),
|
&stake_pool_accounts.staker.pubkey(),
|
||||||
&stake_pool_accounts.withdraw_authority,
|
&stake_pool_accounts.withdraw_authority,
|
||||||
&new_authority,
|
&new_authority,
|
||||||
&wrong_validator_list.pubkey(),
|
&wrong_validator_list.pubkey(),
|
||||||
&user_stake.stake_account,
|
&validator_stake.stake_account,
|
||||||
&user_pool_account.pubkey(),
|
&validator_stake.transient_stake_account,
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
)],
|
||||||
&spl_token::id(),
|
|
||||||
)
|
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
transaction.sign(&[&payer, &stake_pool_accounts.owner], recent_blockhash);
|
transaction.sign(&[&payer, &stake_pool_accounts.staker], recent_blockhash);
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
@ -353,7 +193,7 @@ async fn test_remove_validator_from_pool_with_wrong_validator_list_account() {
|
||||||
_,
|
_,
|
||||||
InstructionError::Custom(error_index),
|
InstructionError::Custom(error_index),
|
||||||
)) => {
|
)) => {
|
||||||
let program_error = error::StakePoolError::InvalidValidatorStakeList as u32;
|
let program_error = StakePoolError::InvalidValidatorStakeList as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong error occurs while try to remove validator stake address with wrong validator stake list account"),
|
_ => panic!("Wrong error occurs while try to remove validator stake address with wrong validator stake list account"),
|
||||||
|
@ -361,26 +201,16 @@ async fn test_remove_validator_from_pool_with_wrong_validator_list_account() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_remove_already_removed_validator_stake_account() {
|
async fn fail_not_at_minimum() {
|
||||||
let (
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
|
||||||
mut banks_client,
|
setup().await;
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
user,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let tokens_to_burn = get_token_balance(&mut banks_client, &user_pool_account.pubkey()).await;
|
transfer(
|
||||||
delegate_tokens(
|
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_pool_account.pubkey(),
|
&validator_stake.stake_account,
|
||||||
&user,
|
1_000_001,
|
||||||
&stake_pool_accounts.withdraw_authority,
|
|
||||||
tokens_to_burn,
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -390,9 +220,36 @@ async fn test_remove_already_removed_validator_stake_account() {
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake.stake_account,
|
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
&new_authority,
|
&new_authority,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
error,
|
||||||
|
TransactionError::InstructionError(
|
||||||
|
0,
|
||||||
|
InstructionError::Custom(StakePoolError::StakeLamportsNotEqualToMinimum as u32)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_double_remove() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
|
||||||
|
setup().await;
|
||||||
|
|
||||||
|
let new_authority = Pubkey::new_unique();
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.remove_validator_from_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&new_authority,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert!(error.is_none());
|
assert!(error.is_none());
|
||||||
|
@ -404,9 +261,9 @@ async fn test_remove_already_removed_validator_stake_account() {
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&latest_blockhash,
|
&latest_blockhash,
|
||||||
&user_stake.stake_account,
|
|
||||||
&user_pool_account.pubkey(),
|
|
||||||
&new_authority,
|
&new_authority,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -416,7 +273,7 @@ async fn test_remove_already_removed_validator_stake_account() {
|
||||||
_,
|
_,
|
||||||
InstructionError::Custom(error_index),
|
InstructionError::Custom(error_index),
|
||||||
)) => {
|
)) => {
|
||||||
let program_error = error::StakePoolError::ValidatorNotFound as u32;
|
let program_error = StakePoolError::ValidatorNotFound as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -426,16 +283,9 @@ async fn test_remove_already_removed_validator_stake_account() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_not_owner_try_to_remove_validator_from_pool() {
|
async fn fail_wrong_staker() {
|
||||||
let (
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
|
||||||
mut banks_client,
|
setup().await;
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
_,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let malicious = Keypair::new();
|
let malicious = Keypair::new();
|
||||||
|
|
||||||
|
@ -448,12 +298,9 @@ async fn test_not_owner_try_to_remove_validator_from_pool() {
|
||||||
&stake_pool_accounts.withdraw_authority,
|
&stake_pool_accounts.withdraw_authority,
|
||||||
&new_authority,
|
&new_authority,
|
||||||
&stake_pool_accounts.validator_list.pubkey(),
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
&user_stake.stake_account,
|
&validator_stake.stake_account,
|
||||||
&user_pool_account.pubkey(),
|
&validator_stake.transient_stake_account,
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
)],
|
||||||
&spl_token::id(),
|
|
||||||
)
|
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
);
|
);
|
||||||
transaction.sign(&[&payer, &malicious], recent_blockhash);
|
transaction.sign(&[&payer, &malicious], recent_blockhash);
|
||||||
|
@ -468,38 +315,31 @@ async fn test_not_owner_try_to_remove_validator_from_pool() {
|
||||||
_,
|
_,
|
||||||
InstructionError::Custom(error_index),
|
InstructionError::Custom(error_index),
|
||||||
)) => {
|
)) => {
|
||||||
let program_error = error::StakePoolError::WrongOwner as u32;
|
let program_error = StakePoolError::WrongStaker as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong error occurs while not an owner try to remove validator stake address"),
|
_ => {
|
||||||
|
panic!("Wrong error occurs while not an staker try to remove validator stake address")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_not_owner_try_to_remove_validator_from_pool_without_signature() {
|
async fn fail_no_signature() {
|
||||||
let (
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
|
||||||
mut banks_client,
|
setup().await;
|
||||||
payer,
|
|
||||||
recent_blockhash,
|
|
||||||
stake_pool_accounts,
|
|
||||||
user_stake,
|
|
||||||
user_pool_account,
|
|
||||||
_,
|
|
||||||
) = setup().await;
|
|
||||||
|
|
||||||
let new_authority = Pubkey::new_unique();
|
let new_authority = Pubkey::new_unique();
|
||||||
|
|
||||||
let accounts = vec![
|
let accounts = vec![
|
||||||
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
|
AccountMeta::new(stake_pool_accounts.stake_pool.pubkey(), false),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.owner.pubkey(), false),
|
AccountMeta::new_readonly(stake_pool_accounts.staker.pubkey(), false),
|
||||||
AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
|
AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
|
||||||
AccountMeta::new_readonly(new_authority, false),
|
AccountMeta::new_readonly(new_authority, false),
|
||||||
AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
|
AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
|
||||||
AccountMeta::new(user_stake.stake_account, false),
|
AccountMeta::new(validator_stake.stake_account, false),
|
||||||
AccountMeta::new(user_pool_account.pubkey(), false),
|
AccountMeta::new_readonly(validator_stake.transient_stake_account, false),
|
||||||
AccountMeta::new(stake_pool_accounts.pool_mint.pubkey(), false),
|
|
||||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
AccountMeta::new_readonly(spl_token::id(), false),
|
|
||||||
AccountMeta::new_readonly(stake_program::id(), false),
|
AccountMeta::new_readonly(stake_program::id(), false),
|
||||||
];
|
];
|
||||||
let instruction = Instruction {
|
let instruction = Instruction {
|
||||||
|
@ -510,8 +350,12 @@ async fn test_not_owner_try_to_remove_validator_from_pool_without_signature() {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
transaction.sign(&[&payer], recent_blockhash);
|
&[instruction],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
@ -523,7 +367,7 @@ async fn test_not_owner_try_to_remove_validator_from_pool_without_signature() {
|
||||||
_,
|
_,
|
||||||
InstructionError::Custom(error_index),
|
InstructionError::Custom(error_index),
|
||||||
)) => {
|
)) => {
|
||||||
let program_error = error::StakePoolError::SignatureMissing as u32;
|
let program_error = StakePoolError::SignatureMissing as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong error occurs while malicious try to remove validator stake account without signing transaction"),
|
_ => panic!("Wrong error occurs while malicious try to remove validator stake account without signing transaction"),
|
||||||
|
@ -531,7 +375,185 @@ async fn test_not_owner_try_to_remove_validator_from_pool_without_signature() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_remove_validator_from_pool_from_unupdated_stake_pool() {} // TODO
|
async fn fail_with_activating_transient_stake() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
|
||||||
|
setup().await;
|
||||||
|
|
||||||
|
// increase the validator stake
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.increase_validator_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
&validator_stake.vote.pubkey(),
|
||||||
|
2_000_000_000,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
let new_authority = Pubkey::new_unique();
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.remove_validator_from_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&new_authority,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
match error {
|
||||||
|
TransactionError::InstructionError(
|
||||||
|
_,
|
||||||
|
InstructionError::Custom(error_index),
|
||||||
|
) => {
|
||||||
|
let program_error = StakePoolError::WrongStakeState as u32;
|
||||||
|
assert_eq!(error_index, program_error);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong error occurs while removing validator stake account while transient stake is activating"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_remove_validator_from_pool_with_uninitialized_validator_list_account() {} // TODO
|
async fn success_with_deactivating_transient_stake() {
|
||||||
|
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
|
||||||
|
setup().await;
|
||||||
|
|
||||||
|
let rent = banks_client.get_rent().await.unwrap();
|
||||||
|
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
|
||||||
|
let deposit_info = simple_deposit(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
&validator_stake,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// increase the validator stake
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.decrease_validator_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
TEST_STAKE_AMOUNT + stake_rent,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
let new_authority = Pubkey::new_unique();
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.remove_validator_from_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&new_authority,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
// fail deposit
|
||||||
|
let maybe_deposit = simple_deposit(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
&validator_stake,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(maybe_deposit.is_none());
|
||||||
|
|
||||||
|
// fail withdraw
|
||||||
|
let user_stake_recipient = Keypair::new();
|
||||||
|
create_blank_stake_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake_recipient,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let user_transfer_authority = Keypair::new();
|
||||||
|
let new_authority = Pubkey::new_unique();
|
||||||
|
delegate_tokens(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
|
&deposit_info.authority,
|
||||||
|
&user_transfer_authority.pubkey(),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.withdraw_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake_recipient.pubkey(),
|
||||||
|
&user_transfer_authority,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&new_authority,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_some());
|
||||||
|
|
||||||
|
// check validator has changed
|
||||||
|
let validator_list = get_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let validator_list =
|
||||||
|
try_from_slice_unchecked::<state::ValidatorList>(validator_list.data.as_slice()).unwrap();
|
||||||
|
let expected_list = state::ValidatorList {
|
||||||
|
account_type: state::AccountType::ValidatorList,
|
||||||
|
max_validators: stake_pool_accounts.max_validators,
|
||||||
|
validators: vec![state::ValidatorStakeInfo {
|
||||||
|
status: state::StakeStatus::DeactivatingTransient,
|
||||||
|
vote_account_address: validator_stake.vote.pubkey(),
|
||||||
|
last_update_epoch: 0,
|
||||||
|
stake_lamports: TEST_STAKE_AMOUNT + stake_rent,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
assert_eq!(validator_list, expected_list);
|
||||||
|
|
||||||
|
// Update, should not change, no merges yet
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.update_all(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&[validator_stake.vote.pubkey()],
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
let validator_list = get_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&stake_pool_accounts.validator_list.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let validator_list =
|
||||||
|
try_from_slice_unchecked::<state::ValidatorList>(validator_list.data.as_slice()).unwrap();
|
||||||
|
assert_eq!(validator_list, expected_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_not_updated_stake_pool() {} // TODO
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_with_uninitialized_validator_list_account() {} // TODO
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
|
bincode::deserialize,
|
||||||
borsh::{BorshDeserialize, BorshSerialize},
|
borsh::{BorshDeserialize, BorshSerialize},
|
||||||
helpers::*,
|
helpers::*,
|
||||||
solana_program::{
|
solana_program::{
|
||||||
|
@ -18,7 +19,8 @@ use {
|
||||||
transport::TransportError,
|
transport::TransportError,
|
||||||
},
|
},
|
||||||
spl_stake_pool::{
|
spl_stake_pool::{
|
||||||
borsh::try_from_slice_unchecked, error, id, instruction, stake_program, state,
|
borsh::try_from_slice_unchecked, error::StakePoolError, id, instruction,
|
||||||
|
minimum_stake_lamports, stake_program, state,
|
||||||
},
|
},
|
||||||
spl_token::error::TokenError,
|
spl_token::error::TokenError,
|
||||||
};
|
};
|
||||||
|
@ -29,17 +31,18 @@ async fn setup() -> (
|
||||||
Hash,
|
Hash,
|
||||||
StakePoolAccounts,
|
StakePoolAccounts,
|
||||||
ValidatorStakeAccount,
|
ValidatorStakeAccount,
|
||||||
DepositInfo,
|
DepositStakeAccount,
|
||||||
|
Keypair,
|
||||||
u64,
|
u64,
|
||||||
) {
|
) {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let validator_stake_account: ValidatorStakeAccount = simple_add_validator_to_pool(
|
let validator_stake_account = simple_add_validator_to_pool(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
|
@ -47,25 +50,28 @@ async fn setup() -> (
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let deposit_info: DepositInfo = simple_deposit(
|
let deposit_info = simple_deposit(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&stake_pool_accounts,
|
&stake_pool_accounts,
|
||||||
&validator_stake_account,
|
&validator_stake_account,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let tokens_to_burn = deposit_info.pool_tokens / 4;
|
let tokens_to_burn = deposit_info.pool_tokens / 4;
|
||||||
|
|
||||||
// Delegate tokens for burning
|
// Delegate tokens for burning
|
||||||
|
let user_transfer_authority = Keypair::new();
|
||||||
delegate_tokens(
|
delegate_tokens(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&deposit_info.user_pool_account,
|
&deposit_info.pool_account.pubkey(),
|
||||||
&deposit_info.user,
|
&deposit_info.authority,
|
||||||
&stake_pool_accounts.withdraw_authority,
|
&user_transfer_authority.pubkey(),
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -77,12 +83,13 @@ async fn setup() -> (
|
||||||
stake_pool_accounts,
|
stake_pool_accounts,
|
||||||
validator_stake_account,
|
validator_stake_account,
|
||||||
deposit_info,
|
deposit_info,
|
||||||
|
user_transfer_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_stake_pool_withdraw() {
|
async fn success() {
|
||||||
let (
|
let (
|
||||||
mut banks_client,
|
mut banks_client,
|
||||||
payer,
|
payer,
|
||||||
|
@ -90,6 +97,7 @@ async fn test_stake_pool_withdraw() {
|
||||||
stake_pool_accounts,
|
stake_pool_accounts,
|
||||||
validator_stake_account,
|
validator_stake_account,
|
||||||
deposit_info,
|
deposit_info,
|
||||||
|
user_transfer_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
) = setup().await;
|
) = setup().await;
|
||||||
|
|
||||||
|
@ -123,33 +131,34 @@ async fn test_stake_pool_withdraw() {
|
||||||
|
|
||||||
// Save user token balance
|
// Save user token balance
|
||||||
let user_token_balance_before =
|
let user_token_balance_before =
|
||||||
get_token_balance(&mut banks_client, &deposit_info.user_pool_account).await;
|
get_token_balance(&mut banks_client, &deposit_info.pool_account.pubkey()).await;
|
||||||
|
|
||||||
let new_authority = Pubkey::new_unique();
|
let new_authority = Pubkey::new_unique();
|
||||||
stake_pool_accounts
|
let error = stake_pool_accounts
|
||||||
.withdraw_stake(
|
.withdraw_stake(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake_recipient.pubkey(),
|
&user_stake_recipient.pubkey(),
|
||||||
&deposit_info.user_pool_account,
|
&user_transfer_authority,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
&new_authority,
|
&new_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
)
|
)
|
||||||
.await
|
.await;
|
||||||
.unwrap();
|
assert!(error.is_none());
|
||||||
|
|
||||||
// Check pool stats
|
// Check pool stats
|
||||||
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
|
let stake_pool = get_account(&mut banks_client, &stake_pool_accounts.stake_pool.pubkey()).await;
|
||||||
let stake_pool = state::StakePool::try_from_slice(&stake_pool.data.as_slice()).unwrap();
|
let stake_pool = state::StakePool::try_from_slice(&stake_pool.data.as_slice()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_pool.stake_total,
|
stake_pool.total_stake_lamports,
|
||||||
stake_pool_before.stake_total - tokens_to_burn
|
stake_pool_before.total_stake_lamports - tokens_to_burn
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stake_pool.pool_total,
|
stake_pool.pool_token_supply,
|
||||||
stake_pool_before.pool_total - tokens_to_burn
|
stake_pool_before.pool_token_supply - tokens_to_burn
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check validator stake list storage
|
// Check validator stake list storage
|
||||||
|
@ -164,13 +173,13 @@ async fn test_stake_pool_withdraw() {
|
||||||
.find(&validator_stake_account.vote.pubkey())
|
.find(&validator_stake_account.vote.pubkey())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
validator_stake_item.balance,
|
validator_stake_item.stake_lamports,
|
||||||
validator_stake_item_before.balance - tokens_to_burn
|
validator_stake_item_before.stake_lamports - tokens_to_burn
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check tokens burned
|
// Check tokens burned
|
||||||
let user_token_balance =
|
let user_token_balance =
|
||||||
get_token_balance(&mut banks_client, &deposit_info.user_pool_account).await;
|
get_token_balance(&mut banks_client, &deposit_info.pool_account.pubkey()).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
user_token_balance,
|
user_token_balance,
|
||||||
user_token_balance_before - tokens_to_burn
|
user_token_balance_before - tokens_to_burn
|
||||||
|
@ -179,9 +188,12 @@ async fn test_stake_pool_withdraw() {
|
||||||
// Check validator stake account balance
|
// Check validator stake account balance
|
||||||
let validator_stake_account =
|
let validator_stake_account =
|
||||||
get_account(&mut banks_client, &validator_stake_account.stake_account).await;
|
get_account(&mut banks_client, &validator_stake_account.stake_account).await;
|
||||||
|
let stake_state =
|
||||||
|
deserialize::<stake_program::StakeState>(&validator_stake_account.data).unwrap();
|
||||||
|
let meta = stake_state.meta().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
validator_stake_account.lamports,
|
validator_stake_account.lamports - minimum_stake_lamports(&meta),
|
||||||
validator_stake_item.balance
|
validator_stake_item.stake_lamports
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check user recipient stake account balance
|
// Check user recipient stake account balance
|
||||||
|
@ -194,7 +206,7 @@ async fn test_stake_pool_withdraw() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_stake_pool_withdraw_with_wrong_stake_program() {
|
async fn fail_with_wrong_stake_program() {
|
||||||
let (
|
let (
|
||||||
mut banks_client,
|
mut banks_client,
|
||||||
payer,
|
payer,
|
||||||
|
@ -202,6 +214,7 @@ async fn test_stake_pool_withdraw_with_wrong_stake_program() {
|
||||||
stake_pool_accounts,
|
stake_pool_accounts,
|
||||||
validator_stake_account,
|
validator_stake_account,
|
||||||
deposit_info,
|
deposit_info,
|
||||||
|
user_transfer_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
) = setup().await;
|
) = setup().await;
|
||||||
|
|
||||||
|
@ -218,7 +231,8 @@ async fn test_stake_pool_withdraw_with_wrong_stake_program() {
|
||||||
AccountMeta::new(validator_stake_account.stake_account, false),
|
AccountMeta::new(validator_stake_account.stake_account, false),
|
||||||
AccountMeta::new(user_stake_recipient.pubkey(), false),
|
AccountMeta::new(user_stake_recipient.pubkey(), false),
|
||||||
AccountMeta::new_readonly(new_authority, false),
|
AccountMeta::new_readonly(new_authority, false),
|
||||||
AccountMeta::new(deposit_info.user_pool_account, false),
|
AccountMeta::new_readonly(user_transfer_authority.pubkey(), true),
|
||||||
|
AccountMeta::new(deposit_info.pool_account.pubkey(), false),
|
||||||
AccountMeta::new(stake_pool_accounts.pool_mint.pubkey(), false),
|
AccountMeta::new(stake_pool_accounts.pool_mint.pubkey(), false),
|
||||||
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
AccountMeta::new_readonly(spl_token::id(), false),
|
AccountMeta::new_readonly(spl_token::id(), false),
|
||||||
|
@ -232,8 +246,12 @@ async fn test_stake_pool_withdraw_with_wrong_stake_program() {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
transaction.sign(&[&payer], recent_blockhash);
|
&[instruction],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer, &user_transfer_authority],
|
||||||
|
recent_blockhash,
|
||||||
|
);
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
@ -249,7 +267,7 @@ async fn test_stake_pool_withdraw_with_wrong_stake_program() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_stake_pool_withdraw_with_wrong_withdraw_authority() {
|
async fn fail_with_wrong_withdraw_authority() {
|
||||||
let (
|
let (
|
||||||
mut banks_client,
|
mut banks_client,
|
||||||
payer,
|
payer,
|
||||||
|
@ -257,6 +275,7 @@ async fn test_stake_pool_withdraw_with_wrong_withdraw_authority() {
|
||||||
mut stake_pool_accounts,
|
mut stake_pool_accounts,
|
||||||
validator_stake_account,
|
validator_stake_account,
|
||||||
deposit_info,
|
deposit_info,
|
||||||
|
user_transfer_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
) = setup().await;
|
) = setup().await;
|
||||||
|
|
||||||
|
@ -272,13 +291,13 @@ async fn test_stake_pool_withdraw_with_wrong_withdraw_authority() {
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake_recipient.pubkey(),
|
&user_stake_recipient.pubkey(),
|
||||||
&deposit_info.user_pool_account,
|
&user_transfer_authority,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
&new_authority,
|
&new_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.err()
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
match transaction_error {
|
match transaction_error {
|
||||||
|
@ -286,7 +305,7 @@ async fn test_stake_pool_withdraw_with_wrong_withdraw_authority() {
|
||||||
_,
|
_,
|
||||||
InstructionError::Custom(error_index),
|
InstructionError::Custom(error_index),
|
||||||
)) => {
|
)) => {
|
||||||
let program_error = error::StakePoolError::InvalidProgramAddress as u32;
|
let program_error = StakePoolError::InvalidProgramAddress as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong error occurs while try to withdraw with wrong withdraw authority"),
|
_ => panic!("Wrong error occurs while try to withdraw with wrong withdraw authority"),
|
||||||
|
@ -294,7 +313,7 @@ async fn test_stake_pool_withdraw_with_wrong_withdraw_authority() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_stake_pool_withdraw_with_wrong_token_program_id() {
|
async fn fail_with_wrong_token_program_id() {
|
||||||
let (
|
let (
|
||||||
mut banks_client,
|
mut banks_client,
|
||||||
payer,
|
payer,
|
||||||
|
@ -302,6 +321,7 @@ async fn test_stake_pool_withdraw_with_wrong_token_program_id() {
|
||||||
stake_pool_accounts,
|
stake_pool_accounts,
|
||||||
validator_stake_account,
|
validator_stake_account,
|
||||||
deposit_info,
|
deposit_info,
|
||||||
|
user_transfer_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
) = setup().await;
|
) = setup().await;
|
||||||
|
|
||||||
|
@ -311,7 +331,7 @@ async fn test_stake_pool_withdraw_with_wrong_token_program_id() {
|
||||||
let new_authority = Pubkey::new_unique();
|
let new_authority = Pubkey::new_unique();
|
||||||
let wrong_token_program = Keypair::new();
|
let wrong_token_program = Keypair::new();
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(
|
let transaction = Transaction::new_signed_with_payer(
|
||||||
&[instruction::withdraw(
|
&[instruction::withdraw(
|
||||||
&id(),
|
&id(),
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
&stake_pool_accounts.stake_pool.pubkey(),
|
||||||
|
@ -320,15 +340,16 @@ async fn test_stake_pool_withdraw_with_wrong_token_program_id() {
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
&user_stake_recipient.pubkey(),
|
&user_stake_recipient.pubkey(),
|
||||||
&new_authority,
|
&new_authority,
|
||||||
&deposit_info.user_pool_account,
|
&user_transfer_authority.pubkey(),
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
&stake_pool_accounts.pool_mint.pubkey(),
|
&stake_pool_accounts.pool_mint.pubkey(),
|
||||||
&wrong_token_program.pubkey(),
|
&wrong_token_program.pubkey(),
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
)
|
)],
|
||||||
.unwrap()],
|
|
||||||
Some(&payer.pubkey()),
|
Some(&payer.pubkey()),
|
||||||
|
&[&payer, &user_transfer_authority],
|
||||||
|
recent_blockhash,
|
||||||
);
|
);
|
||||||
transaction.sign(&[&payer], recent_blockhash);
|
|
||||||
let transaction_error = banks_client
|
let transaction_error = banks_client
|
||||||
.process_transaction(transaction)
|
.process_transaction(transaction)
|
||||||
.await
|
.await
|
||||||
|
@ -344,7 +365,7 @@ async fn test_stake_pool_withdraw_with_wrong_token_program_id() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_stake_pool_withdraw_with_wrong_validator_list() {
|
async fn fail_with_wrong_validator_list() {
|
||||||
let (
|
let (
|
||||||
mut banks_client,
|
mut banks_client,
|
||||||
payer,
|
payer,
|
||||||
|
@ -352,6 +373,7 @@ async fn test_stake_pool_withdraw_with_wrong_validator_list() {
|
||||||
mut stake_pool_accounts,
|
mut stake_pool_accounts,
|
||||||
validator_stake_account,
|
validator_stake_account,
|
||||||
deposit_info,
|
deposit_info,
|
||||||
|
user_transfer_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
) = setup().await;
|
) = setup().await;
|
||||||
|
|
||||||
|
@ -367,13 +389,13 @@ async fn test_stake_pool_withdraw_with_wrong_validator_list() {
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake_recipient.pubkey(),
|
&user_stake_recipient.pubkey(),
|
||||||
&deposit_info.user_pool_account,
|
&user_transfer_authority,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
&new_authority,
|
&new_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.err()
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
match transaction_error {
|
match transaction_error {
|
||||||
|
@ -381,7 +403,7 @@ async fn test_stake_pool_withdraw_with_wrong_validator_list() {
|
||||||
_,
|
_,
|
||||||
InstructionError::Custom(error_index),
|
InstructionError::Custom(error_index),
|
||||||
)) => {
|
)) => {
|
||||||
let program_error = error::StakePoolError::InvalidValidatorStakeList as u32;
|
let program_error = StakePoolError::InvalidValidatorStakeList as u32;
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!(
|
_ => panic!(
|
||||||
|
@ -391,37 +413,36 @@ async fn test_stake_pool_withdraw_with_wrong_validator_list() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_stake_pool_withdraw_from_unknown_validator() {
|
async fn fail_with_unknown_validator() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
mut banks_client,
|
||||||
stake_pool_accounts
|
payer,
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
recent_blockhash,
|
||||||
.await
|
stake_pool_accounts,
|
||||||
.unwrap();
|
_,
|
||||||
|
_,
|
||||||
|
user_transfer_authority,
|
||||||
|
_,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
let validator_stake_account = ValidatorStakeAccount::new_with_target_authority(
|
let validator_stake_account =
|
||||||
&stake_pool_accounts.deposit_authority,
|
ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey());
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
|
||||||
);
|
|
||||||
validator_stake_account
|
validator_stake_account
|
||||||
.create_and_delegate(
|
.create_and_delegate(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&stake_pool_accounts.owner,
|
&stake_pool_accounts.staker,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let user_stake = ValidatorStakeAccount::new_with_target_authority(
|
let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey());
|
||||||
&stake_pool_accounts.deposit_authority,
|
|
||||||
&stake_pool_accounts.stake_pool.pubkey(),
|
|
||||||
);
|
|
||||||
user_stake
|
user_stake
|
||||||
.create_and_delegate(
|
.create_and_delegate(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&stake_pool_accounts.owner,
|
&stake_pool_accounts.staker,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -453,6 +474,7 @@ async fn test_stake_pool_withdraw_from_unknown_validator() {
|
||||||
&user_stake,
|
&user_stake,
|
||||||
&authorized,
|
&authorized,
|
||||||
&lockup,
|
&lockup,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
// make pool token account
|
// make pool token account
|
||||||
|
@ -480,7 +502,7 @@ async fn test_stake_pool_withdraw_from_unknown_validator() {
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_pool_account,
|
&user_pool_account,
|
||||||
&user,
|
&user,
|
||||||
&stake_pool_accounts.withdraw_authority,
|
&user_transfer_authority.pubkey(),
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -496,21 +518,19 @@ async fn test_stake_pool_withdraw_from_unknown_validator() {
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake_recipient.pubkey(),
|
&user_stake_recipient.pubkey(),
|
||||||
|
&user_transfer_authority,
|
||||||
&user_pool_account,
|
&user_pool_account,
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
&new_authority,
|
&new_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.err()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
match transaction_error {
|
match transaction_error {
|
||||||
TransportError::TransactionError(TransactionError::InstructionError(
|
TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => {
|
||||||
_,
|
let program_error = StakePoolError::ValidatorNotFound as u32;
|
||||||
InstructionError::Custom(error_index),
|
|
||||||
)) => {
|
|
||||||
let program_error = error::StakePoolError::ValidatorNotFound as u32;
|
|
||||||
assert_eq!(error_index, program_error);
|
assert_eq!(error_index, program_error);
|
||||||
}
|
}
|
||||||
_ => panic!("Wrong error occurs while try to do withdraw from unknown validator"),
|
_ => panic!("Wrong error occurs while try to do withdraw from unknown validator"),
|
||||||
|
@ -518,7 +538,7 @@ async fn test_stake_pool_withdraw_from_unknown_validator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_stake_pool_double_withdraw_to_the_same_account() {
|
async fn fail_double_withdraw_to_the_same_account() {
|
||||||
let (
|
let (
|
||||||
mut banks_client,
|
mut banks_client,
|
||||||
payer,
|
payer,
|
||||||
|
@ -526,6 +546,7 @@ async fn test_stake_pool_double_withdraw_to_the_same_account() {
|
||||||
stake_pool_accounts,
|
stake_pool_accounts,
|
||||||
validator_stake_account,
|
validator_stake_account,
|
||||||
deposit_info,
|
deposit_info,
|
||||||
|
user_transfer_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
) = setup().await;
|
) = setup().await;
|
||||||
|
|
||||||
|
@ -540,35 +561,48 @@ async fn test_stake_pool_double_withdraw_to_the_same_account() {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let new_authority = Pubkey::new_unique();
|
let new_authority = Pubkey::new_unique();
|
||||||
stake_pool_accounts
|
let error = stake_pool_accounts
|
||||||
.withdraw_stake(
|
.withdraw_stake(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake_recipient.pubkey(),
|
&user_stake_recipient.pubkey(),
|
||||||
&deposit_info.user_pool_account,
|
&user_transfer_authority,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
&new_authority,
|
&new_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
)
|
)
|
||||||
.await
|
.await;
|
||||||
.unwrap();
|
assert!(error.is_none());
|
||||||
|
|
||||||
let latest_blockhash = banks_client.get_recent_blockhash().await.unwrap();
|
let latest_blockhash = banks_client.get_recent_blockhash().await.unwrap();
|
||||||
|
|
||||||
|
// Delegate tokens for burning
|
||||||
|
delegate_tokens(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&latest_blockhash,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
|
&deposit_info.authority,
|
||||||
|
&user_transfer_authority.pubkey(),
|
||||||
|
tokens_to_burn,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let transaction_error = stake_pool_accounts
|
let transaction_error = stake_pool_accounts
|
||||||
.withdraw_stake(
|
.withdraw_stake(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&latest_blockhash,
|
&latest_blockhash,
|
||||||
&user_stake_recipient.pubkey(),
|
&user_stake_recipient.pubkey(),
|
||||||
&deposit_info.user_pool_account,
|
&user_transfer_authority,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
&new_authority,
|
&new_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.err()
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
match transaction_error {
|
match transaction_error {
|
||||||
|
@ -580,15 +614,15 @@ async fn test_stake_pool_double_withdraw_to_the_same_account() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_stake_pool_withdraw_token_delegate_was_not_setup() {
|
async fn fail_without_token_approval() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let validator_stake_account: ValidatorStakeAccount = simple_add_validator_to_pool(
|
let validator_stake_account = simple_add_validator_to_pool(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
|
@ -596,14 +630,16 @@ async fn test_stake_pool_withdraw_token_delegate_was_not_setup() {
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let deposit_info: DepositInfo = simple_deposit(
|
let deposit_info = simple_deposit(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&stake_pool_accounts,
|
&stake_pool_accounts,
|
||||||
&validator_stake_account,
|
&validator_stake_account,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let tokens_to_burn = deposit_info.pool_tokens / 4;
|
let tokens_to_burn = deposit_info.pool_tokens / 4;
|
||||||
|
|
||||||
|
@ -618,19 +654,20 @@ async fn test_stake_pool_withdraw_token_delegate_was_not_setup() {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let new_authority = Pubkey::new_unique();
|
let new_authority = Pubkey::new_unique();
|
||||||
|
let user_transfer_authority = Keypair::new();
|
||||||
let transaction_error = stake_pool_accounts
|
let transaction_error = stake_pool_accounts
|
||||||
.withdraw_stake(
|
.withdraw_stake(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake_recipient.pubkey(),
|
&user_stake_recipient.pubkey(),
|
||||||
&deposit_info.user_pool_account,
|
&user_transfer_authority,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
&new_authority,
|
&new_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.err()
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
match transaction_error {
|
match transaction_error {
|
||||||
|
@ -648,15 +685,15 @@ async fn test_stake_pool_withdraw_token_delegate_was_not_setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_stake_pool_withdraw_with_low_delegation() {
|
async fn fail_with_low_delegation() {
|
||||||
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
let (mut banks_client, payer, recent_blockhash) = program_test().start().await;
|
||||||
let stake_pool_accounts = StakePoolAccounts::new();
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
stake_pool_accounts
|
stake_pool_accounts
|
||||||
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash)
|
.initialize_stake_pool(&mut banks_client, &payer, &recent_blockhash, 1)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let validator_stake_account: ValidatorStakeAccount = simple_add_validator_to_pool(
|
let validator_stake_account = simple_add_validator_to_pool(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
|
@ -664,25 +701,28 @@ async fn test_stake_pool_withdraw_with_low_delegation() {
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let deposit_info: DepositInfo = simple_deposit(
|
let deposit_info = simple_deposit(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&stake_pool_accounts,
|
&stake_pool_accounts,
|
||||||
&validator_stake_account,
|
&validator_stake_account,
|
||||||
|
TEST_STAKE_AMOUNT,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let tokens_to_burn = deposit_info.pool_tokens / 4;
|
let tokens_to_burn = deposit_info.pool_tokens / 4;
|
||||||
|
|
||||||
|
let user_transfer_authority = Keypair::new();
|
||||||
// Delegate tokens for burning
|
// Delegate tokens for burning
|
||||||
delegate_tokens(
|
delegate_tokens(
|
||||||
&mut banks_client,
|
&mut banks_client,
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&deposit_info.user_pool_account,
|
&deposit_info.pool_account.pubkey(),
|
||||||
&deposit_info.user,
|
&deposit_info.authority,
|
||||||
&stake_pool_accounts.withdraw_authority,
|
&user_transfer_authority.pubkey(),
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -704,13 +744,13 @@ async fn test_stake_pool_withdraw_with_low_delegation() {
|
||||||
&payer,
|
&payer,
|
||||||
&recent_blockhash,
|
&recent_blockhash,
|
||||||
&user_stake_recipient.pubkey(),
|
&user_stake_recipient.pubkey(),
|
||||||
&deposit_info.user_pool_account,
|
&user_transfer_authority,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
&validator_stake_account.stake_account,
|
&validator_stake_account.stake_account,
|
||||||
&new_authority,
|
&new_authority,
|
||||||
tokens_to_burn,
|
tokens_to_burn,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.err()
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
match transaction_error {
|
match transaction_error {
|
||||||
|
@ -726,3 +766,250 @@ async fn test_stake_pool_withdraw_with_low_delegation() {
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn fail_overdraw_validator() {
|
||||||
|
let (
|
||||||
|
mut banks_client,
|
||||||
|
payer,
|
||||||
|
recent_blockhash,
|
||||||
|
stake_pool_accounts,
|
||||||
|
_validator_stake_account,
|
||||||
|
deposit_info,
|
||||||
|
user_transfer_authority,
|
||||||
|
tokens_to_burn,
|
||||||
|
) = setup().await;
|
||||||
|
|
||||||
|
// Create stake account to withdraw to
|
||||||
|
let user_stake_recipient = Keypair::new();
|
||||||
|
let _initial_stake_lamports = create_blank_stake_account(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake_recipient,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let validator_stake_account = simple_add_validator_to_pool(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let new_authority = Pubkey::new_unique();
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.withdraw_stake(
|
||||||
|
&mut banks_client,
|
||||||
|
&payer,
|
||||||
|
&recent_blockhash,
|
||||||
|
&user_stake_recipient.pubkey(),
|
||||||
|
&user_transfer_authority,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
|
&validator_stake_account.stake_account,
|
||||||
|
&new_authority,
|
||||||
|
tokens_to_burn,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
error,
|
||||||
|
TransactionError::InstructionError(
|
||||||
|
0,
|
||||||
|
InstructionError::Custom(StakePoolError::StakeLamportsNotEqualToMinimum as u32)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn success_with_reserve() {
|
||||||
|
let mut context = program_test().start_with_context().await;
|
||||||
|
let stake_pool_accounts = StakePoolAccounts::new();
|
||||||
|
let initial_reserve_lamports = 1;
|
||||||
|
stake_pool_accounts
|
||||||
|
.initialize_stake_pool(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
initial_reserve_lamports,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let validator_stake = simple_add_validator_to_pool(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let deposit_lamports = TEST_STAKE_AMOUNT;
|
||||||
|
let rent = context.banks_client.get_rent().await.unwrap();
|
||||||
|
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
|
||||||
|
|
||||||
|
let deposit_info = simple_deposit(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&stake_pool_accounts,
|
||||||
|
&validator_stake,
|
||||||
|
deposit_lamports,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// decrease some stake
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.decrease_validator_stake(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
deposit_lamports - 1,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
// warp forward to deactivation
|
||||||
|
let first_normal_slot = context.genesis_config().epoch_schedule.first_normal_slot;
|
||||||
|
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
|
||||||
|
context
|
||||||
|
.warp_to_slot(first_normal_slot + slots_per_epoch)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// update to merge deactivated stake into reserve
|
||||||
|
stake_pool_accounts
|
||||||
|
.update_all(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&[validator_stake.vote.pubkey()],
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Delegate tokens for burning during withdraw
|
||||||
|
let user_transfer_authority = Keypair::new();
|
||||||
|
delegate_tokens(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
|
&deposit_info.authority,
|
||||||
|
&user_transfer_authority.pubkey(),
|
||||||
|
deposit_info.pool_tokens,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Withdraw directly from reserve, fail because some stake left
|
||||||
|
let withdraw_destination = Keypair::new();
|
||||||
|
let withdraw_destination_authority = Pubkey::new_unique();
|
||||||
|
let initial_stake_lamports = create_blank_stake_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&withdraw_destination,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.withdraw_stake(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&withdraw_destination.pubkey(),
|
||||||
|
&user_transfer_authority,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&withdraw_destination_authority,
|
||||||
|
deposit_info.pool_tokens,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
error,
|
||||||
|
TransactionError::InstructionError(
|
||||||
|
0,
|
||||||
|
InstructionError::Custom(StakePoolError::StakeLamportsNotEqualToMinimum as u32)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// decrease rest of stake
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.decrease_validator_stake(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&validator_stake.stake_account,
|
||||||
|
&validator_stake.transient_stake_account,
|
||||||
|
stake_rent + 1,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
// warp forward to deactivation
|
||||||
|
context
|
||||||
|
.warp_to_slot(first_normal_slot + 2 * slots_per_epoch)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// update to merge deactivated stake into reserve
|
||||||
|
stake_pool_accounts
|
||||||
|
.update_all(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&[validator_stake.vote.pubkey()],
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// now it works
|
||||||
|
let error = stake_pool_accounts
|
||||||
|
.withdraw_stake(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&context.payer,
|
||||||
|
&context.last_blockhash,
|
||||||
|
&withdraw_destination.pubkey(),
|
||||||
|
&user_transfer_authority,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
&withdraw_destination_authority,
|
||||||
|
deposit_info.pool_tokens,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(error.is_none());
|
||||||
|
|
||||||
|
// Check tokens burned
|
||||||
|
let user_token_balance = get_token_balance(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&deposit_info.pool_account.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(user_token_balance, 0);
|
||||||
|
|
||||||
|
// Check reserve stake account balance
|
||||||
|
let reserve_stake_account = get_account(
|
||||||
|
&mut context.banks_client,
|
||||||
|
&stake_pool_accounts.reserve_stake.pubkey(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let stake_state =
|
||||||
|
deserialize::<stake_program::StakeState>(&reserve_stake_account.data).unwrap();
|
||||||
|
let meta = stake_state.meta().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
initial_reserve_lamports + meta.rent_exempt_reserve,
|
||||||
|
reserve_stake_account.lamports
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check user recipient stake account balance
|
||||||
|
let user_stake_recipient_account =
|
||||||
|
get_account(&mut context.banks_client, &withdraw_destination.pubkey()).await;
|
||||||
|
assert_eq!(
|
||||||
|
user_stake_recipient_account.lamports,
|
||||||
|
initial_stake_lamports + deposit_info.stake_lamports + stake_rent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -11,13 +11,13 @@ exclude = ["js/**"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = "1.3"
|
bincode = "1.3"
|
||||||
borsh = "0.7.1"
|
borsh = "0.8"
|
||||||
curve25519-dalek = {package = "curve25519-dalek-ng", git = "https://github.com/garious/curve25519-dalek", rev = "fcef1fa11b3d3e89a1abf8986386ba9ae375392c", default-features = false, features = ["borsh"]}
|
curve25519-dalek = {package = "curve25519-dalek-ng", git = "https://github.com/garious/curve25519-dalek", rev = "fcef1fa11b3d3e89a1abf8986386ba9ae375392c", default-features = false, features = ["borsh"]}
|
||||||
elgamal_ristretto = { git = "https://github.com/garious/elgamal", rev = "892dbe115104bcb8cc26d79f9676c836ff6c018e", default-features = false }
|
elgamal_ristretto = { git = "https://github.com/garious/elgamal", rev = "892dbe115104bcb8cc26d79f9676c836ff6c018e", default-features = false }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
solana-banks-client = "1.6.2"
|
solana-banks-client = "1.6.7"
|
||||||
solana-cli-config = "1.6.2"
|
solana-cli-config = "1.6.7"
|
||||||
solana-sdk = "1.6.2"
|
solana-sdk = "1.6.7"
|
||||||
spl-themis-ristretto = { version = "0.1.0", path = "../program_ristretto", features = ["no-entrypoint"] }
|
spl-themis-ristretto = { version = "0.1.0", path = "../program_ristretto", features = ["no-entrypoint"] }
|
||||||
tarpc = { version = "0.22.0", features = ["full"] }
|
tarpc = { version = "0.22.0", features = ["full"] }
|
||||||
tokio = "0.3"
|
tokio = "0.3"
|
||||||
|
@ -25,11 +25,11 @@ url = "2.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
separator = "0.4.1"
|
separator = "0.4.1"
|
||||||
solana-banks-server = "1.6.2"
|
solana-banks-server = "1.6.7"
|
||||||
solana-bpf-loader-program = "1.6.2"
|
solana-bpf-loader-program = "1.6.7"
|
||||||
solana-core = "1.6.2"
|
solana-core = "1.6.7"
|
||||||
solana_rbpf = "0.1"
|
solana_rbpf = "0.1"
|
||||||
solana-runtime = "1.6.2"
|
solana-runtime = "1.6.7"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
|
@ -20,7 +20,7 @@ getrandom = { version = "0.1.15", features = ["dummy"] }
|
||||||
num-derive = "0.3"
|
num-derive = "0.3"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
rand = "0.8.0"
|
rand = "0.8.0"
|
||||||
solana-program = "1.6.2"
|
solana-program = "1.6.7"
|
||||||
subtle = "=2.2.3"
|
subtle = "=2.2.3"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue