Compare commits
122 Commits
master
...
stake-pool
Author | SHA1 | Date |
---|---|---|
|
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,4 @@ node_modules
|
||||||
hfuzz_target
|
hfuzz_target
|
||||||
hfuzz_workspace
|
hfuzz_workspace
|
||||||
**/*.so
|
**/*.so
|
||||||
|
**/.DS_Store
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -11,12 +11,12 @@ members = [
|
||||||
"feature-proposal/cli",
|
"feature-proposal/cli",
|
||||||
"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 +29,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,15 @@ 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"
|
futures = "0.3"
|
||||||
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"]
|
||||||
|
|
|
@ -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>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,13 +68,13 @@ 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 = get_account_data::<spl_token::state::Mint>(&mut banks_client, mint_address)
|
||||||
|
@ -115,12 +115,12 @@ 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,
|
get_account_data::<FeatureProposal>(&mut banks_client, feature_proposal.pubkey()).await,
|
||||||
|
@ -158,12 +158,12 @@ 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!(
|
||||||
|
|
|
@ -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.1.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"
|
||||||
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "spl-token-lending-client"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Solana Program Library Token Lending Client"
|
|
||||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
|
||||||
repository = "https://github.com/solana-labs/solana-program-library"
|
|
||||||
license = "Apache-2.0"
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
solana-client = "1.6.2"
|
|
||||||
solana-program = "1.6.2"
|
|
||||||
solana-sdk = "1.6.2"
|
|
||||||
spl-token-lending = { path = "../program", features = [ "no-entrypoint" ] }
|
|
||||||
spl-token = { path = "../../token/program", features = [ "no-entrypoint" ] }
|
|
|
@ -1,304 +0,0 @@
|
||||||
use solana_client::rpc_client::RpcClient;
|
|
||||||
use solana_program::program_pack::Pack;
|
|
||||||
use solana_sdk::{
|
|
||||||
pubkey::Pubkey,
|
|
||||||
signature::{read_keypair_file, Keypair, Signer},
|
|
||||||
system_instruction::create_account,
|
|
||||||
transaction::Transaction,
|
|
||||||
};
|
|
||||||
use spl_token::{
|
|
||||||
instruction::approve,
|
|
||||||
state::{Account as Token, Mint},
|
|
||||||
};
|
|
||||||
use spl_token_lending::{
|
|
||||||
instruction::{init_lending_market, init_reserve},
|
|
||||||
state::{LendingMarket, Reserve, ReserveConfig, ReserveFees},
|
|
||||||
};
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
// -------- UPDATE START -------
|
|
||||||
const KEYPAIR_PATH: &str = "/your/path";
|
|
||||||
const SRM_TOKEN_ACCOUNT: &str = "BASE58_ADDRESS";
|
|
||||||
const USDC_TOKEN_ACCOUNT: &str = "BASE58_ADDRESS";
|
|
||||||
const WRAPPED_SOL_TOKEN_ACCOUNT: &str = "BASE58_ADDRESS";
|
|
||||||
solana_program::declare_id!("TokenLend1ng1111111111111111111111111111111");
|
|
||||||
// -------- UPDATE END ---------
|
|
||||||
|
|
||||||
pub struct DexMarket {
|
|
||||||
pub name: &'static str,
|
|
||||||
pub pubkey: Pubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let mut client = RpcClient::new("https://api.mainnet-beta.solana.com".to_owned());
|
|
||||||
|
|
||||||
let payer = read_keypair_file(&format!("{}/payer.json", KEYPAIR_PATH)).unwrap();
|
|
||||||
let usdc_mint_pubkey =
|
|
||||||
Pubkey::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v").unwrap();
|
|
||||||
|
|
||||||
let sol_usdc_dex_market = DexMarket {
|
|
||||||
name: "sol_usdc",
|
|
||||||
pubkey: Pubkey::from_str("9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT").unwrap(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let srm_usdc_dex_market = DexMarket {
|
|
||||||
name: "srm_usdc",
|
|
||||||
pubkey: Pubkey::from_str("ByRys5tuUWDgL73G8JBAEfkdFf8JWBzPBDHsBVQ5vbQA").unwrap(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let quote_token_mint = usdc_mint_pubkey;
|
|
||||||
let (lending_market_owner, lending_market_pubkey, _lending_market) =
|
|
||||||
create_lending_market(&mut client, quote_token_mint, &payer);
|
|
||||||
|
|
||||||
let usdc_liquidity_source = Pubkey::from_str(USDC_TOKEN_ACCOUNT).unwrap();
|
|
||||||
let usdc_reserve_config = ReserveConfig {
|
|
||||||
optimal_utilization_rate: 80,
|
|
||||||
loan_to_value_ratio: 75,
|
|
||||||
liquidation_bonus: 5,
|
|
||||||
liquidation_threshold: 80,
|
|
||||||
min_borrow_rate: 0,
|
|
||||||
optimal_borrow_rate: 4,
|
|
||||||
max_borrow_rate: 30,
|
|
||||||
fees: ReserveFees {
|
|
||||||
borrow_fee_wad: 100_000_000_000_000, // 1 bp
|
|
||||||
host_fee_percentage: 20,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let (usdc_reserve_pubkey, _usdc_reserve) = create_reserve(
|
|
||||||
&mut client,
|
|
||||||
usdc_reserve_config,
|
|
||||||
lending_market_pubkey,
|
|
||||||
&lending_market_owner,
|
|
||||||
None,
|
|
||||||
usdc_liquidity_source,
|
|
||||||
&payer,
|
|
||||||
);
|
|
||||||
|
|
||||||
println!("Created usdc reserve with pubkey: {}", usdc_reserve_pubkey);
|
|
||||||
|
|
||||||
let sol_liquidity_source = Pubkey::from_str(WRAPPED_SOL_TOKEN_ACCOUNT).unwrap();
|
|
||||||
let sol_reserve_config = ReserveConfig {
|
|
||||||
optimal_utilization_rate: 0,
|
|
||||||
loan_to_value_ratio: 75,
|
|
||||||
liquidation_bonus: 10,
|
|
||||||
liquidation_threshold: 80,
|
|
||||||
min_borrow_rate: 0,
|
|
||||||
optimal_borrow_rate: 2,
|
|
||||||
max_borrow_rate: 15,
|
|
||||||
fees: ReserveFees {
|
|
||||||
borrow_fee_wad: 1_000_000_000_000, // 0.01 bp
|
|
||||||
host_fee_percentage: 20,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let (sol_reserve_pubkey, _sol_reserve) = create_reserve(
|
|
||||||
&mut client,
|
|
||||||
sol_reserve_config,
|
|
||||||
lending_market_pubkey,
|
|
||||||
&lending_market_owner,
|
|
||||||
Some(sol_usdc_dex_market.pubkey),
|
|
||||||
sol_liquidity_source,
|
|
||||||
&payer,
|
|
||||||
);
|
|
||||||
|
|
||||||
println!("Created sol reserve with pubkey: {}", sol_reserve_pubkey);
|
|
||||||
|
|
||||||
let srm_liquidity_source = Pubkey::from_str(SRM_TOKEN_ACCOUNT).unwrap();
|
|
||||||
let srm_reserve_config = ReserveConfig {
|
|
||||||
optimal_utilization_rate: 0,
|
|
||||||
loan_to_value_ratio: 75,
|
|
||||||
liquidation_bonus: 10,
|
|
||||||
liquidation_threshold: 80,
|
|
||||||
min_borrow_rate: 0,
|
|
||||||
optimal_borrow_rate: 2,
|
|
||||||
max_borrow_rate: 15,
|
|
||||||
fees: ReserveFees {
|
|
||||||
borrow_fee_wad: 10_000_000_000_000, // 0.1 bp
|
|
||||||
host_fee_percentage: 25,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let (srm_reserve_pubkey, _srm_reserve) = create_reserve(
|
|
||||||
&mut client,
|
|
||||||
srm_reserve_config,
|
|
||||||
lending_market_pubkey,
|
|
||||||
&lending_market_owner,
|
|
||||||
Some(srm_usdc_dex_market.pubkey),
|
|
||||||
srm_liquidity_source,
|
|
||||||
&payer,
|
|
||||||
);
|
|
||||||
|
|
||||||
println!("Created srm reserve with pubkey: {}", srm_reserve_pubkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_lending_market(
|
|
||||||
client: &mut RpcClient,
|
|
||||||
quote_token_mint: Pubkey,
|
|
||||||
payer: &Keypair,
|
|
||||||
) -> (Keypair, Pubkey, LendingMarket) {
|
|
||||||
let owner = read_keypair_file(&format!("{}/lending_market_owner.json", KEYPAIR_PATH)).unwrap();
|
|
||||||
let keypair = Keypair::new();
|
|
||||||
let pubkey = keypair.pubkey();
|
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(
|
|
||||||
&[
|
|
||||||
create_account(
|
|
||||||
&payer.pubkey(),
|
|
||||||
&pubkey,
|
|
||||||
client
|
|
||||||
.get_minimum_balance_for_rent_exemption(LendingMarket::LEN)
|
|
||||||
.unwrap(),
|
|
||||||
LendingMarket::LEN as u64,
|
|
||||||
&id(),
|
|
||||||
),
|
|
||||||
init_lending_market(id(), pubkey, owner.pubkey(), quote_token_mint),
|
|
||||||
],
|
|
||||||
Some(&payer.pubkey()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let recent_blockhash = client.get_recent_blockhash().unwrap().0;
|
|
||||||
transaction.sign(&[&payer, &keypair], recent_blockhash);
|
|
||||||
client.send_and_confirm_transaction(&transaction).unwrap();
|
|
||||||
|
|
||||||
let account = client.get_account(&pubkey).unwrap();
|
|
||||||
let lending_market = LendingMarket::unpack(&account.data).unwrap();
|
|
||||||
|
|
||||||
(owner, pubkey, lending_market)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_reserve(
|
|
||||||
client: &mut RpcClient,
|
|
||||||
config: ReserveConfig,
|
|
||||||
lending_market_pubkey: Pubkey,
|
|
||||||
lending_market_owner: &Keypair,
|
|
||||||
dex_market_pubkey: Option<Pubkey>,
|
|
||||||
liquidity_source_pubkey: Pubkey,
|
|
||||||
payer: &Keypair,
|
|
||||||
) -> (Pubkey, Reserve) {
|
|
||||||
let reserve_keypair = Keypair::new();
|
|
||||||
let reserve_pubkey = reserve_keypair.pubkey();
|
|
||||||
let collateral_mint_keypair = Keypair::new();
|
|
||||||
let collateral_supply_keypair = Keypair::new();
|
|
||||||
let collateral_fees_receiver_keypair = Keypair::new();
|
|
||||||
let liquidity_supply_keypair = Keypair::new();
|
|
||||||
let user_collateral_token_keypair = Keypair::new();
|
|
||||||
let user_transfer_authority = Keypair::new();
|
|
||||||
|
|
||||||
let liquidity_source_account = client.get_account(&liquidity_source_pubkey).unwrap();
|
|
||||||
let liquidity_source_token = Token::unpack(&liquidity_source_account.data).unwrap();
|
|
||||||
let liquidity_mint_pubkey = liquidity_source_token.mint;
|
|
||||||
|
|
||||||
let recent_blockhash = client.get_recent_blockhash().unwrap().0;
|
|
||||||
let token_balance = client
|
|
||||||
.get_minimum_balance_for_rent_exemption(Token::LEN)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(
|
|
||||||
&[
|
|
||||||
create_account(
|
|
||||||
&payer.pubkey(),
|
|
||||||
&collateral_mint_keypair.pubkey(),
|
|
||||||
client
|
|
||||||
.get_minimum_balance_for_rent_exemption(Mint::LEN)
|
|
||||||
.unwrap(),
|
|
||||||
Mint::LEN as u64,
|
|
||||||
&spl_token::id(),
|
|
||||||
),
|
|
||||||
create_account(
|
|
||||||
&payer.pubkey(),
|
|
||||||
&collateral_supply_keypair.pubkey(),
|
|
||||||
token_balance,
|
|
||||||
Token::LEN as u64,
|
|
||||||
&spl_token::id(),
|
|
||||||
),
|
|
||||||
create_account(
|
|
||||||
&payer.pubkey(),
|
|
||||||
&collateral_fees_receiver_keypair.pubkey(),
|
|
||||||
token_balance,
|
|
||||||
Token::LEN as u64,
|
|
||||||
&spl_token::id(),
|
|
||||||
),
|
|
||||||
create_account(
|
|
||||||
&payer.pubkey(),
|
|
||||||
&liquidity_supply_keypair.pubkey(),
|
|
||||||
token_balance,
|
|
||||||
Token::LEN as u64,
|
|
||||||
&spl_token::id(),
|
|
||||||
),
|
|
||||||
create_account(
|
|
||||||
&payer.pubkey(),
|
|
||||||
&user_collateral_token_keypair.pubkey(),
|
|
||||||
token_balance,
|
|
||||||
Token::LEN as u64,
|
|
||||||
&spl_token::id(),
|
|
||||||
),
|
|
||||||
create_account(
|
|
||||||
&payer.pubkey(),
|
|
||||||
&reserve_pubkey,
|
|
||||||
client
|
|
||||||
.get_minimum_balance_for_rent_exemption(Reserve::LEN)
|
|
||||||
.unwrap(),
|
|
||||||
Reserve::LEN as u64,
|
|
||||||
&id(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
Some(&payer.pubkey()),
|
|
||||||
);
|
|
||||||
|
|
||||||
transaction.sign(
|
|
||||||
&vec![
|
|
||||||
payer,
|
|
||||||
&reserve_keypair,
|
|
||||||
&collateral_mint_keypair,
|
|
||||||
&collateral_supply_keypair,
|
|
||||||
&liquidity_supply_keypair,
|
|
||||||
&user_collateral_token_keypair,
|
|
||||||
],
|
|
||||||
recent_blockhash,
|
|
||||||
);
|
|
||||||
|
|
||||||
client.send_and_confirm_transaction(&transaction).unwrap();
|
|
||||||
|
|
||||||
let mut transaction = Transaction::new_with_payer(
|
|
||||||
&[
|
|
||||||
approve(
|
|
||||||
&spl_token::id(),
|
|
||||||
&liquidity_source_pubkey,
|
|
||||||
&user_transfer_authority.pubkey(),
|
|
||||||
&payer.pubkey(),
|
|
||||||
&[],
|
|
||||||
liquidity_source_token.amount,
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
init_reserve(
|
|
||||||
id(),
|
|
||||||
liquidity_source_token.amount,
|
|
||||||
config,
|
|
||||||
liquidity_source_pubkey,
|
|
||||||
user_collateral_token_keypair.pubkey(),
|
|
||||||
reserve_pubkey,
|
|
||||||
liquidity_mint_pubkey,
|
|
||||||
liquidity_supply_keypair.pubkey(),
|
|
||||||
collateral_mint_keypair.pubkey(),
|
|
||||||
collateral_supply_keypair.pubkey(),
|
|
||||||
collateral_fees_receiver_keypair.pubkey(),
|
|
||||||
lending_market_pubkey,
|
|
||||||
lending_market_owner.pubkey(),
|
|
||||||
user_transfer_authority.pubkey(),
|
|
||||||
dex_market_pubkey,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
Some(&payer.pubkey()),
|
|
||||||
);
|
|
||||||
|
|
||||||
transaction.sign(
|
|
||||||
&vec![payer, &lending_market_owner, &user_transfer_authority],
|
|
||||||
recent_blockhash,
|
|
||||||
);
|
|
||||||
|
|
||||||
client.send_and_confirm_transaction(&transaction).unwrap();
|
|
||||||
|
|
||||||
let account = client.get_account(&reserve_pubkey).unwrap();
|
|
||||||
(reserve_pubkey, Reserve::unpack(&account.data).unwrap())
|
|
||||||
}
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
# Flash Loan Design
|
||||||
|
|
||||||
|
We added a new instruction with the following signature for flash loan:
|
||||||
|
```rust
|
||||||
|
pub enum LendingInstruction {
|
||||||
|
// ....
|
||||||
|
/// Make a flash loan.
|
||||||
|
///
|
||||||
|
/// Accounts expected by this instruction:
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Source liquidity token account.
|
||||||
|
/// Minted by reserve liquidity mint.
|
||||||
|
/// Must match the reserve liquidity supply.
|
||||||
|
/// 1. `[writable]` Destination liquidity token account.
|
||||||
|
/// Minted by reserve liquidity mint.
|
||||||
|
/// 2. `[writable]` Reserve account.
|
||||||
|
/// 3. `[]` Lending market account.
|
||||||
|
/// 4. `[]` Derived lending market authority.
|
||||||
|
/// 5. `[]` Flash loan receiver program account.
|
||||||
|
/// Must implement an instruction that has tag of 0 and a signature of `(repay_amount: u64)`
|
||||||
|
/// This instruction must return the amount to the source liquidity account.
|
||||||
|
/// 6. `[]` Token program id.
|
||||||
|
/// 7. `[writable]` Flash loan fee receiver account.
|
||||||
|
/// Must match the reserve liquidity fee receiver.
|
||||||
|
/// 8. `[writable]` Host fee receiver.
|
||||||
|
/// .. `[any]` Additional accounts expected by the receiving program's `ReceiveFlashLoan` instruction.
|
||||||
|
FlashLoan {
|
||||||
|
/// The amount that is to be borrowed
|
||||||
|
amount: u64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the implementation, we do the following in order:
|
||||||
|
|
||||||
|
1. Perform safety checks and calculate fees
|
||||||
|
2. Transfer `amount` from the source liquidity account to the destination liquidity account
|
||||||
|
2. Call the `ReceiveFlashLoan` function (the flash loan receiver program is required to have this function with tag `0`).
|
||||||
|
The additional account required for `ReceiveFlashLoan` is given from the 10th account of the `FlashLoan` instruction, i.e. after host fee receiver.
|
||||||
|
3. Check that the returned amount with the fee is in the reserve account after the completion of `ReceiveFlashLoan` function.
|
||||||
|
|
||||||
|
The flash loan receiver program should have a `ReceiveFlashLoan` instruction which executes the user-defined operation and return the funds to the reserve in the end.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum FlashLoanReceiverInstruction {
|
||||||
|
|
||||||
|
/// Receive a flash loan and perform user-defined operation and finally return the fund back.
|
||||||
|
///
|
||||||
|
/// Accounts expected:
|
||||||
|
///
|
||||||
|
/// 0. `[writable]` Source liquidity (matching the destination from above).
|
||||||
|
/// 1. `[writable]` Destination liquidity (matching the source from above).
|
||||||
|
/// 2. `[]` Token program id
|
||||||
|
/// .. `[any]` Additional accounts provided to the lending program's `FlashLoan` instruction above.
|
||||||
|
ReceiveFlashLoan {
|
||||||
|
// Amount that is loaned to the receiver program
|
||||||
|
amount: u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
You can view a sample implementation [here](https://github.com/solana-labs/solana-program-library/tree/master/token-lending/program/tests/helpers/flash_loan_receiver.rs).
|
|
@ -30,7 +30,7 @@ export const LendingMarketLayout: typeof BufferLayout.Structure = BufferLayout.s
|
||||||
Layout.publicKey("owner"),
|
Layout.publicKey("owner"),
|
||||||
Layout.publicKey("quoteTokenMint"),
|
Layout.publicKey("quoteTokenMint"),
|
||||||
Layout.publicKey("tokenProgramId"),
|
Layout.publicKey("tokenProgramId"),
|
||||||
BufferLayout.blob(62, "padding"),
|
BufferLayout.blob(128, "padding"),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -24,20 +24,33 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-validator-identifier": {
|
"@babel/helper-validator-identifier": {
|
||||||
"version": "7.12.11",
|
"version": "7.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz",
|
||||||
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
|
"integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/highlight": {
|
"@babel/highlight": {
|
||||||
"version": "7.13.10",
|
"version": "7.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz",
|
||||||
"integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==",
|
"integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-validator-identifier": "^7.12.11",
|
"@babel/helper-validator-identifier": "^7.14.0",
|
||||||
"chalk": "^2.0.0",
|
"chalk": "^2.0.0",
|
||||||
"js-tokens": "^4.0.0"
|
"js-tokens": "^4.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^3.2.1",
|
||||||
|
"escape-string-regexp": "^1.0.5",
|
||||||
|
"supports-color": "^5.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/runtime": {
|
"@babel/runtime": {
|
||||||
|
@ -49,9 +62,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@eslint/eslintrc": {
|
"@eslint/eslintrc": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz",
|
||||||
"integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==",
|
"integrity": "sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
|
@ -118,9 +131,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@rollup/plugin-commonjs": {
|
"@rollup/plugin-commonjs": {
|
||||||
"version": "18.0.0",
|
"version": "19.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-18.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-19.0.0.tgz",
|
||||||
"integrity": "sha512-fj92shhg8luw7XbA0HowAqz90oo7qtLGwqTKbyZ8pmOyH8ui5e+u0wPEgeHLH3djcVma6gUCUrjY6w5R2o1u6g==",
|
"integrity": "sha512-adTpD6ATGbehdaQoZQ6ipDFhdjqsTgpOAhFiPwl+dzre4pPshsecptDPyEFb61JMJ1+mGljktaC4jI8ARMSNyw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@rollup/pluginutils": "^3.1.0",
|
"@rollup/pluginutils": "^3.1.0",
|
||||||
|
@ -173,49 +186,18 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@solana/spl-token": {
|
"@solana/spl-token": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.1.4.tgz",
|
||||||
"integrity": "sha512-M251on5RDz8VQXoKoQPeLANEyI4qhThKLZBeUiLbFZ93KRgouGfmV5D/bUZXkLF75PlLcARIzU9ptoHOlZ6SbQ==",
|
"integrity": "sha512-W8uSC4ysWVjbKK7lvsIHFxdMIkOCEoOm9tYY9VVpBlUIp4+K5bpPxHXUlxMiHfkKPWAxab6IGUn71VVLg8uq5Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.10.5",
|
"@babel/runtime": "^7.10.5",
|
||||||
"@solana/web3.js": "^1.2.2",
|
"@solana/web3.js": "^1.9.1",
|
||||||
"bn.js": "^5.1.0",
|
"bn.js": "^5.1.0",
|
||||||
"buffer": "6.0.3",
|
"buffer": "6.0.3",
|
||||||
"buffer-layout": "^1.2.0",
|
"buffer-layout": "^1.2.0",
|
||||||
"dotenv": "8.2.0"
|
"dotenv": "8.2.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@solana/web3.js": {
|
|
||||||
"version": "1.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.2.4.tgz",
|
|
||||||
"integrity": "sha512-qTYmZiFPrk6ZWniIzLCwa/4+Ovlg6DXLc32Q1SPqqQldnnoZkrCBuZq8mcd8VkPC2pVAwJhxsLTY3I9i5O8tuQ==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.12.5",
|
|
||||||
"bn.js": "^5.0.0",
|
|
||||||
"bs58": "^4.0.1",
|
|
||||||
"buffer": "6.0.1",
|
|
||||||
"buffer-layout": "^1.2.0",
|
|
||||||
"crypto-hash": "^1.2.2",
|
|
||||||
"jayson": "^3.4.4",
|
|
||||||
"js-sha3": "^0.8.0",
|
|
||||||
"node-fetch": "^2.6.1",
|
|
||||||
"rpc-websockets": "^7.4.2",
|
|
||||||
"secp256k1": "^4.0.2",
|
|
||||||
"superstruct": "^0.14.2",
|
|
||||||
"tweetnacl": "^1.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"buffer": {
|
|
||||||
"version": "6.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.1.tgz",
|
|
||||||
"integrity": "sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ==",
|
|
||||||
"requires": {
|
|
||||||
"base64-js": "^1.3.1",
|
|
||||||
"ieee754": "^1.2.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"buffer": {
|
"buffer": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
|
@ -224,13 +206,18 @@
|
||||||
"base64-js": "^1.3.1",
|
"base64-js": "^1.3.1",
|
||||||
"ieee754": "^1.2.1"
|
"ieee754": "^1.2.1"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"dotenv": {
|
||||||
|
"version": "8.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
|
||||||
|
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@solana/web3.js": {
|
"@solana/web3.js": {
|
||||||
"version": "1.2.5",
|
"version": "1.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.10.1.tgz",
|
||||||
"integrity": "sha512-dkKIDhmSM9qX5eJs4bNWPLMLzWcSCd36Imj4IIIfJHKwMfhjO6sWfYjHD4dzB502GoqpvZ/jHv4JBxZR5ERSiA==",
|
"integrity": "sha512-5zepm+AaVfC6uao/WxKT7SlemqgGEp+x7XUv+018RURIx+XEcF+G2ZgjrIbjy5OoXJyfz+wGw8Or2fxsypderA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"bn.js": "^5.0.0",
|
"bn.js": "^5.0.0",
|
||||||
|
@ -263,17 +250,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/connect": {
|
"@types/connect": {
|
||||||
"version": "3.4.33",
|
"version": "3.4.34",
|
||||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz",
|
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz",
|
||||||
"integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==",
|
"integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/eslint": {
|
"@types/eslint": {
|
||||||
"version": "7.2.8",
|
"version": "7.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.10.tgz",
|
||||||
"integrity": "sha512-RTKvBsfz0T8CKOGZMfuluDNyMFHnu5lvNr4hWEsQeHXH6FcmIDIozOyWMh36nLGMwVd5UFNXC2xztA8lln22MQ==",
|
"integrity": "sha512-kUEPnMKrqbtpCq/KTaGFFKAcz6Ethm2EjCoKIDaCmfRBWLbFuTcOJfTlorwbnboXBzahqWLgUp1BQeKHiJzPUQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/estree": "*",
|
"@types/estree": "*",
|
||||||
|
@ -296,9 +283,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/express-serve-static-core": {
|
"@types/express-serve-static-core": {
|
||||||
"version": "4.17.13",
|
"version": "4.17.19",
|
||||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz",
|
||||||
"integrity": "sha512-RgDi5a4nuzam073lRGKTUIaL3eF2+H7LJvJ8eUnCI0wA6SNjXc44DCmWNiTLs/AZ7QlsFWZiw/gTG3nSQGL0fA==",
|
"integrity": "sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"@types/qs": "*",
|
"@types/qs": "*",
|
||||||
|
@ -312,9 +299,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/lodash": {
|
"@types/lodash": {
|
||||||
"version": "4.14.165",
|
"version": "4.14.168",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.165.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz",
|
||||||
"integrity": "sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg=="
|
"integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q=="
|
||||||
},
|
},
|
||||||
"@types/mkdirp": {
|
"@types/mkdirp": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -335,9 +322,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "14.14.37",
|
"version": "15.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz",
|
||||||
"integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw=="
|
"integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA=="
|
||||||
},
|
},
|
||||||
"@types/prettier": {
|
"@types/prettier": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
|
@ -346,9 +333,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/qs": {
|
"@types/qs": {
|
||||||
"version": "6.9.5",
|
"version": "6.9.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz",
|
||||||
"integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ=="
|
"integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA=="
|
||||||
},
|
},
|
||||||
"@types/range-parser": {
|
"@types/range-parser": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
|
@ -387,172 +374,92 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/eslint-plugin": {
|
"@typescript-eslint/eslint-plugin": {
|
||||||
"version": "4.20.0",
|
"version": "4.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.23.0.tgz",
|
||||||
"integrity": "sha512-sw+3HO5aehYqn5w177z2D82ZQlqHCwcKSMboueo7oE4KU9QiC0SAgfS/D4z9xXvpTc8Bt41Raa9fBR8T2tIhoQ==",
|
"integrity": "sha512-tGK1y3KIvdsQEEgq6xNn1DjiFJtl+wn8JJQiETtCbdQxw1vzjXyAaIkEmO2l6Nq24iy3uZBMFQjZ6ECf1QdgGw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/experimental-utils": "4.20.0",
|
"@typescript-eslint/experimental-utils": "4.23.0",
|
||||||
"@typescript-eslint/scope-manager": "4.20.0",
|
"@typescript-eslint/scope-manager": "4.23.0",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"functional-red-black-tree": "^1.0.1",
|
"functional-red-black-tree": "^1.0.1",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"regexpp": "^3.0.0",
|
"regexpp": "^3.0.0",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.2",
|
||||||
"tsutils": "^3.17.1"
|
"tsutils": "^3.17.1"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"semver": {
|
|
||||||
"version": "7.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
|
||||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"lru-cache": "^6.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/experimental-utils": {
|
"@typescript-eslint/experimental-utils": {
|
||||||
"version": "4.20.0",
|
"version": "4.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.23.0.tgz",
|
||||||
"integrity": "sha512-sQNlf6rjLq2yB5lELl3gOE7OuoA/6IVXJUJ+Vs7emrQMva14CkOwyQwD7CW+TkmOJ4Q/YGmoDLmbfFrpGmbKng==",
|
"integrity": "sha512-WAFNiTDnQfrF3Z2fQ05nmCgPsO5o790vOhmWKXbbYQTO9erE1/YsFot5/LnOUizLzU2eeuz6+U/81KV5/hFTGA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/json-schema": "^7.0.3",
|
"@types/json-schema": "^7.0.3",
|
||||||
"@typescript-eslint/scope-manager": "4.20.0",
|
"@typescript-eslint/scope-manager": "4.23.0",
|
||||||
"@typescript-eslint/types": "4.20.0",
|
"@typescript-eslint/types": "4.23.0",
|
||||||
"@typescript-eslint/typescript-estree": "4.20.0",
|
"@typescript-eslint/typescript-estree": "4.23.0",
|
||||||
"eslint-scope": "^5.0.0",
|
"eslint-scope": "^5.0.0",
|
||||||
"eslint-utils": "^2.0.0"
|
"eslint-utils": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/parser": {
|
"@typescript-eslint/parser": {
|
||||||
"version": "4.20.0",
|
"version": "4.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.23.0.tgz",
|
||||||
"integrity": "sha512-m6vDtgL9EABdjMtKVw5rr6DdeMCH3OA1vFb0dAyuZSa3e5yw1YRzlwFnm9knma9Lz6b2GPvoNSa8vOXrqsaglA==",
|
"integrity": "sha512-wsvjksHBMOqySy/Pi2Q6UuIuHYbgAMwLczRl4YanEPKW5KVxI9ZzDYh3B5DtcZPQTGRWFJrfcbJ6L01Leybwug==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/scope-manager": "4.20.0",
|
"@typescript-eslint/scope-manager": "4.23.0",
|
||||||
"@typescript-eslint/types": "4.20.0",
|
"@typescript-eslint/types": "4.23.0",
|
||||||
"@typescript-eslint/typescript-estree": "4.20.0",
|
"@typescript-eslint/typescript-estree": "4.23.0",
|
||||||
"debug": "^4.1.1"
|
"debug": "^4.1.1"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@typescript-eslint/scope-manager": {
|
|
||||||
"version": "4.20.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.20.0.tgz",
|
|
||||||
"integrity": "sha512-/zm6WR6iclD5HhGpcwl/GOYDTzrTHmvf8LLLkwKqqPKG6+KZt/CfSgPCiybshmck66M2L5fWSF/MKNuCwtKQSQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@typescript-eslint/types": "4.20.0",
|
|
||||||
"@typescript-eslint/visitor-keys": "4.20.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@typescript-eslint/types": {
|
|
||||||
"version": "4.20.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.20.0.tgz",
|
|
||||||
"integrity": "sha512-cYY+1PIjei1nk49JAPnH1VEnu7OYdWRdJhYI5wiKOUMhLTG1qsx5cQxCUTuwWCmQoyriadz3Ni8HZmGSofeC+w==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"@typescript-eslint/typescript-estree": {
|
|
||||||
"version": "4.20.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.20.0.tgz",
|
|
||||||
"integrity": "sha512-Knpp0reOd4ZsyoEJdW8i/sK3mtZ47Ls7ZHvD8WVABNx5Xnn7KhenMTRGegoyMTx6TiXlOVgMz9r0pDgXTEEIHA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@typescript-eslint/types": "4.20.0",
|
|
||||||
"@typescript-eslint/visitor-keys": "4.20.0",
|
|
||||||
"debug": "^4.1.1",
|
|
||||||
"globby": "^11.0.1",
|
|
||||||
"is-glob": "^4.0.1",
|
|
||||||
"semver": "^7.3.2",
|
|
||||||
"tsutils": "^3.17.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@typescript-eslint/visitor-keys": {
|
|
||||||
"version": "4.20.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.20.0.tgz",
|
|
||||||
"integrity": "sha512-NXKRM3oOVQL8yNFDNCZuieRIwZ5UtjNLYtmMx2PacEAGmbaEYtGgVHUHVyZvU/0rYZcizdrWjDo+WBtRPSgq+A==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@typescript-eslint/types": "4.20.0",
|
|
||||||
"eslint-visitor-keys": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"eslint-visitor-keys": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"semver": {
|
|
||||||
"version": "7.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
|
||||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"lru-cache": "^6.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/scope-manager": {
|
"@typescript-eslint/scope-manager": {
|
||||||
"version": "4.20.0",
|
"version": "4.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.23.0.tgz",
|
||||||
"integrity": "sha512-/zm6WR6iclD5HhGpcwl/GOYDTzrTHmvf8LLLkwKqqPKG6+KZt/CfSgPCiybshmck66M2L5fWSF/MKNuCwtKQSQ==",
|
"integrity": "sha512-ZZ21PCFxPhI3n0wuqEJK9omkw51wi2bmeKJvlRZPH5YFkcawKOuRMQMnI8mH6Vo0/DoHSeZJnHiIx84LmVQY+w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.20.0",
|
"@typescript-eslint/types": "4.23.0",
|
||||||
"@typescript-eslint/visitor-keys": "4.20.0"
|
"@typescript-eslint/visitor-keys": "4.23.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/types": {
|
"@typescript-eslint/types": {
|
||||||
"version": "4.20.0",
|
"version": "4.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.23.0.tgz",
|
||||||
"integrity": "sha512-cYY+1PIjei1nk49JAPnH1VEnu7OYdWRdJhYI5wiKOUMhLTG1qsx5cQxCUTuwWCmQoyriadz3Ni8HZmGSofeC+w==",
|
"integrity": "sha512-oqkNWyG2SLS7uTWLZf6Sr7Dm02gA5yxiz1RP87tvsmDsguVATdpVguHr4HoGOcFOpCvx9vtCSCyQUGfzq28YCw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@typescript-eslint/typescript-estree": {
|
"@typescript-eslint/typescript-estree": {
|
||||||
"version": "4.20.0",
|
"version": "4.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.23.0.tgz",
|
||||||
"integrity": "sha512-Knpp0reOd4ZsyoEJdW8i/sK3mtZ47Ls7ZHvD8WVABNx5Xnn7KhenMTRGegoyMTx6TiXlOVgMz9r0pDgXTEEIHA==",
|
"integrity": "sha512-5Sty6zPEVZF5fbvrZczfmLCOcby3sfrSPu30qKoY1U3mca5/jvU5cwsPb/CO6Q3ByRjixTMIVsDkqwIxCf/dMw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.20.0",
|
"@typescript-eslint/types": "4.23.0",
|
||||||
"@typescript-eslint/visitor-keys": "4.20.0",
|
"@typescript-eslint/visitor-keys": "4.23.0",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"globby": "^11.0.1",
|
"globby": "^11.0.1",
|
||||||
"is-glob": "^4.0.1",
|
"is-glob": "^4.0.1",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.2",
|
||||||
"tsutils": "^3.17.1"
|
"tsutils": "^3.17.1"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"semver": {
|
|
||||||
"version": "7.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
|
||||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"lru-cache": "^6.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/visitor-keys": {
|
"@typescript-eslint/visitor-keys": {
|
||||||
"version": "4.20.0",
|
"version": "4.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.23.0.tgz",
|
||||||
"integrity": "sha512-NXKRM3oOVQL8yNFDNCZuieRIwZ5UtjNLYtmMx2PacEAGmbaEYtGgVHUHVyZvU/0rYZcizdrWjDo+WBtRPSgq+A==",
|
"integrity": "sha512-5PNe5cmX9pSifit0H+nPoQBXdbNzi5tOEec+3riK+ku4e3er37pKxMKDH5Ct5Y4fhWxcD4spnlYjxi9vXbSpwg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.20.0",
|
"@typescript-eslint/types": "4.23.0",
|
||||||
"eslint-visitor-keys": "^2.0.0"
|
"eslint-visitor-keys": "^2.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eslint-visitor-keys": {
|
"eslint-visitor-keys": {
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
|
||||||
"integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==",
|
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -764,9 +671,9 @@
|
||||||
"integrity": "sha512-iiyRoho/ERzBUv6kFvfsrLNgTlVwOkqQcSQN7WrO3Y+c5SeuEhCn6+y1KwhM0V3ndptF5mI/RI44zkw0qcR5Jg=="
|
"integrity": "sha512-iiyRoho/ERzBUv6kFvfsrLNgTlVwOkqQcSQN7WrO3Y+c5SeuEhCn6+y1KwhM0V3ndptF5mI/RI44zkw0qcR5Jg=="
|
||||||
},
|
},
|
||||||
"bufferutil": {
|
"bufferutil": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz",
|
||||||
"integrity": "sha512-AtnG3W6M8B2n4xDQ5R+70EXvOpnXsFYg/AK2yTZd+HQ/oxAdz+GI+DvjmhBw3L0ole+LJ0ngqY4JMbDzkfNzhA==",
|
"integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"node-gyp-build": "^4.2.0"
|
"node-gyp-build": "^4.2.0"
|
||||||
|
@ -778,16 +685,6 @@
|
||||||
"integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==",
|
"integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"call-bind": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"function-bind": "^1.1.1",
|
|
||||||
"get-intrinsic": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"callsites": {
|
"callsites": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
|
@ -795,14 +692,54 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "2.4.2",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
|
||||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-styles": "^3.2.1",
|
"ansi-styles": "^4.1.0",
|
||||||
"escape-string-regexp": "^1.0.5",
|
"supports-color": "^7.1.0"
|
||||||
"supports-color": "^5.3.0"
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"check-more-types": {
|
"check-more-types": {
|
||||||
|
@ -953,9 +890,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dotenv": {
|
"dotenv": {
|
||||||
"version": "8.2.0",
|
"version": "9.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz",
|
||||||
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
|
"integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"duplexer": {
|
"duplexer": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
|
@ -1028,13 +966,13 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"eslint": {
|
"eslint": {
|
||||||
"version": "7.23.0",
|
"version": "7.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.26.0.tgz",
|
||||||
"integrity": "sha512-kqvNVbdkjzpFy0XOszNwjkKzZ+6TcwCQ/h+ozlcIWwaimBBuhlQ4nN6kbiM2L+OjDcznkTJxzYfRFH92sx4a0Q==",
|
"integrity": "sha512-4R1ieRf52/izcZE7AlLy56uIHHDLT74Yzz2Iv2l6kDaYvEu9x+wMB5dZArVL8SYGXSYV2YAg70FcW5Y5nGGNIg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "7.12.11",
|
"@babel/code-frame": "7.12.11",
|
||||||
"@eslint/eslintrc": "^0.4.0",
|
"@eslint/eslintrc": "^0.4.1",
|
||||||
"ajv": "^6.10.0",
|
"ajv": "^6.10.0",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"cross-spawn": "^7.0.2",
|
"cross-spawn": "^7.0.2",
|
||||||
|
@ -1072,75 +1010,11 @@
|
||||||
"v8-compile-cache": "^2.0.3"
|
"v8-compile-cache": "^2.0.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": {
|
|
||||||
"version": "4.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"color-convert": "^2.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"chalk": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"ansi-styles": "^4.1.0",
|
|
||||||
"supports-color": "^7.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"color-convert": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"color-name": "~1.1.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"color-name": {
|
|
||||||
"version": "1.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"eslint-visitor-keys": {
|
"eslint-visitor-keys": {
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
|
||||||
"integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==",
|
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
|
||||||
"has-flag": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"lodash": {
|
|
||||||
"version": "4.17.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"semver": {
|
|
||||||
"version": "7.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
|
||||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"lru-cache": "^6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"supports-color": {
|
|
||||||
"version": "7.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
|
||||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"has-flag": "^4.0.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1151,9 +1025,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"eslint-plugin-prettier": {
|
"eslint-plugin-prettier": {
|
||||||
"version": "3.3.1",
|
"version": "3.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz",
|
||||||
"integrity": "sha512-Rq3jkcFY8RYeQLgk2cCwuc0P7SEFwDravPhsJZOQ5N4YI4DSg50NyqJ/9gdZHzQlHf8MvafSesbNJCcP/FF6pQ==",
|
"integrity": "sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"prettier-linter-helpers": "^1.0.0"
|
"prettier-linter-helpers": "^1.0.0"
|
||||||
|
@ -1446,17 +1320,6 @@
|
||||||
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
|
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"get-intrinsic": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"function-bind": "^1.1.1",
|
|
||||||
"has": "^1.0.3",
|
|
||||||
"has-symbols": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"get-prototype-of": {
|
"get-prototype-of": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-prototype-of/-/get-prototype-of-0.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-prototype-of/-/get-prototype-of-0.0.0.tgz",
|
||||||
|
@ -1495,9 +1358,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"version": "13.7.0",
|
"version": "13.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-13.8.0.tgz",
|
||||||
"integrity": "sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA==",
|
"integrity": "sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"type-fest": "^0.20.2"
|
"type-fest": "^0.20.2"
|
||||||
|
@ -1554,12 +1417,6 @@
|
||||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"has-symbols": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"hash.js": {
|
"hash.js": {
|
||||||
"version": "1.1.7",
|
"version": "1.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
|
||||||
|
@ -1627,15 +1484,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
},
|
},
|
||||||
"is-boolean-object": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"call-bind": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-capitalized": {
|
"is-capitalized": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-capitalized/-/is-capitalized-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-capitalized/-/is-capitalized-1.0.0.tgz",
|
||||||
|
@ -1688,12 +1536,6 @@
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"is-number-object": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"is-reference": {
|
"is-reference": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
|
||||||
|
@ -1709,12 +1551,6 @@
|
||||||
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
|
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"is-string": {
|
|
||||||
"version": "1.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
|
|
||||||
"integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"isexe": {
|
"isexe": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
|
@ -1722,9 +1558,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"jayson": {
|
"jayson": {
|
||||||
"version": "3.4.4",
|
"version": "3.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/jayson/-/jayson-3.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/jayson/-/jayson-3.6.2.tgz",
|
||||||
"integrity": "sha512-fgQflh+Qnhdv9fjxTnpTsa2WUG/dgyeKQzIh5MJ77Qv2sqFyyAZn7mTUYgPjJMFjsKfb4HNsSBh6ktJeeQiAGQ==",
|
"integrity": "sha512-hbl+x2xH6FT7nckw+Pq3lKOIJaMBKOgNJEVfvloDBWB8iSfzn/1U2igj1A5rplqNMFN/OnnaTNw8qPKVmoq83Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/connect": "^3.4.33",
|
"@types/connect": "^3.4.33",
|
||||||
"@types/express-serve-static-core": "^4.17.9",
|
"@types/express-serve-static-core": "^4.17.9",
|
||||||
|
@ -1740,9 +1576,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "12.20.7",
|
"version": "12.20.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.12.tgz",
|
||||||
"integrity": "sha512-gWL8VUkg8VRaCAUgG9WmhefMqHmMblxe2rVpMF86nZY/+ZysU+BkAp+3cz03AixWDSSz0ks5WX59yAhv/cDwFA=="
|
"integrity": "sha512-KQZ1al2hKOONAs2MFv+yTQP1LkDWMrRJ9YCVRalXltOfXsBmH5IownLxQaiq0lnAHwAViLnh2aTYqrPcRGEbgg=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1845,9 +1681,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
},
|
},
|
||||||
"lodash.clonedeep": {
|
"lodash.clonedeep": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
|
@ -1855,12 +1691,6 @@
|
||||||
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
|
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash.flatten": {
|
|
||||||
"version": "4.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
|
||||||
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"lodash.truncate": {
|
"lodash.truncate": {
|
||||||
"version": "4.4.2",
|
"version": "4.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
|
||||||
|
@ -2227,9 +2057,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rollup": {
|
"rollup": {
|
||||||
"version": "2.44.0",
|
"version": "2.47.0",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.44.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.47.0.tgz",
|
||||||
"integrity": "sha512-rGSF4pLwvuaH/x4nAS+zP6UNn5YUDWf/TeEU5IoXSZKBbKRNTCI3qMnYXKZgrC0D2KzS2baiOZt1OlqhMu5rnQ==",
|
"integrity": "sha512-rqBjgq9hQfW0vRmz+0S062ORRNJXvwRpzxhFXORvar/maZqY6za3rgQ/p1Glg+j1hnc1GtYyQCPiAei95uTElg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fsevents": "~2.3.1"
|
"fsevents": "~2.3.1"
|
||||||
|
@ -2332,9 +2162,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rpc-websockets": {
|
"rpc-websockets": {
|
||||||
"version": "7.4.6",
|
"version": "7.4.11",
|
||||||
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-7.4.11.tgz",
|
||||||
"integrity": "sha512-vDGdyJv858O5ZIc7glov8pQDdFztOqujA7iNyrfPxw87ajHT5s8WQU4MLNEG8pTR/xzqOn06dYH7kef2hijInw==",
|
"integrity": "sha512-/6yKCkRrEEb+TlJb6Q/pNBD4WdO/tFxE22rQYBl1YyIgz3SpzQDQ/0qAMWWksjFkDayiq3xVxmkP8e/tL422ZA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.11.2",
|
"@babel/runtime": "^7.11.2",
|
||||||
"assert-args": "^1.2.1",
|
"assert-args": "^1.2.1",
|
||||||
|
@ -2347,9 +2177,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"version": "8.3.1",
|
"version": "8.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2383,6 +2213,15 @@
|
||||||
"node-gyp-build": "^4.2.0"
|
"node-gyp-build": "^4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"semver": {
|
||||||
|
"version": "7.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||||
|
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"lru-cache": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"shebang-command": {
|
"shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
@ -2576,26 +2415,23 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"version": "6.0.9",
|
"version": "6.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/table/-/table-6.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/table/-/table-6.7.0.tgz",
|
||||||
"integrity": "sha512-F3cLs9a3hL1Z7N4+EkSscsel3z55XT950AvB05bwayrNg5T1/gykXtigioTAjbltvbMSJvvhFCbnf6mX+ntnJQ==",
|
"integrity": "sha512-SAM+5p6V99gYiiy2gT5ArdzgM1dLDed0nkrWmG6Fry/bUS/m9x83BwpJUOf1Qj/x2qJd+thL6IkIx7qPGRxqBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "^8.0.1",
|
"ajv": "^8.0.1",
|
||||||
"is-boolean-object": "^1.1.0",
|
|
||||||
"is-number-object": "^1.0.4",
|
|
||||||
"is-string": "^1.0.5",
|
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"lodash.flatten": "^4.4.0",
|
|
||||||
"lodash.truncate": "^4.4.2",
|
"lodash.truncate": "^4.4.2",
|
||||||
"slice-ansi": "^4.0.0",
|
"slice-ansi": "^4.0.0",
|
||||||
"string-width": "^4.2.0"
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "8.0.1",
|
"version": "8.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.3.0.tgz",
|
||||||
"integrity": "sha512-46ZA4TalFcLLqX1dEU3dhdY38wAtDydJ4e7QQTVekLUTzXkb1LfqU6VOBXC/a9wiv4T094WURqJH6ZitF92Kqw==",
|
"integrity": "sha512-RYE7B5An83d7eWnDR8kbdaIFqmKCNsP16ay1hDbJEU+sa0e3H9SebskCt0Uufem6cfAVu7Col6ubcn/W+Sm8/Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
|
@ -2687,9 +2523,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
|
||||||
"integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==",
|
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"universalify": {
|
"universalify": {
|
||||||
|
@ -2708,9 +2544,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"utf-8-validate": {
|
"utf-8-validate": {
|
||||||
"version": "5.0.3",
|
"version": "5.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.5.tgz",
|
||||||
"integrity": "sha512-jtJM6fpGv8C1SoH4PtG22pGto6x+Y8uPprW0tw3//gGFhDDTiuksgradgFN6yRayDP4SyZZa6ZMGHLIa17+M8A==",
|
"integrity": "sha512-+pnxRYsS/axEpkrrEpzYfNZGXp0IjC/9RIxwM5gntY4Koi8SHmUGSfxfWqxZdRxrtaoVstuOzUp/rbs3JSPELQ==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"node-gyp-build": "^4.2.0"
|
"node-gyp-build": "^4.2.0"
|
||||||
|
@ -2738,14 +2574,6 @@
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"rxjs": "^6.6.3"
|
"rxjs": "^6.6.3"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"lodash": {
|
|
||||||
"version": "4.17.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"word-wrap": {
|
"word-wrap": {
|
||||||
|
@ -2761,9 +2589,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ws": {
|
"ws": {
|
||||||
"version": "7.4.0",
|
"version": "7.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
|
||||||
"integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ=="
|
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g=="
|
||||||
},
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
|
|
@ -34,37 +34,37 @@
|
||||||
"cluster:mainnet-beta": "cp cluster-mainnet-beta.env .env"
|
"cluster:mainnet-beta": "cp cluster-mainnet-beta.env .env"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@solana/spl-token": "0.1.3",
|
"@solana/spl-token": "0.1.4",
|
||||||
"@solana/web3.js": "^1.2.5",
|
"@solana/web3.js": "^1.10.1",
|
||||||
"bn.js": "^5.2.0",
|
"bn.js": "^5.2.0",
|
||||||
"mkdirp": "^1.0.4"
|
"mkdirp": "^1.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^18.0.0",
|
"@rollup/plugin-commonjs": "^19.0.0",
|
||||||
"@tsconfig/recommended": "^1.0.1",
|
"@tsconfig/recommended": "^1.0.1",
|
||||||
"@types/bn.js": "^5.1.0",
|
"@types/bn.js": "^5.1.0",
|
||||||
"@types/eslint": "^7.2.8",
|
"@types/eslint": "^7.2.10",
|
||||||
"@types/eslint-plugin-prettier": "^3.1.0",
|
"@types/eslint-plugin-prettier": "^3.1.0",
|
||||||
"@types/mkdirp": "^1.0.1",
|
"@types/mkdirp": "^1.0.1",
|
||||||
"@types/mz": "^2.7.3",
|
"@types/mz": "^2.7.3",
|
||||||
"@types/node": "^14.14.37",
|
"@types/node": "^15.0.2",
|
||||||
"@types/prettier": "^2.2.3",
|
"@types/prettier": "^2.2.3",
|
||||||
"@types/rollup-plugin-json": "^3.0.2",
|
"@types/rollup-plugin-json": "^3.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.20.0",
|
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
||||||
"@typescript-eslint/parser": "^4.20.0",
|
"@typescript-eslint/parser": "^4.23.0",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^9.0.2",
|
||||||
"eslint": "^7.23.0",
|
"eslint": "^7.26.0",
|
||||||
"eslint-config-prettier": "^7.2.0",
|
"eslint-config-prettier": "^7.2.0",
|
||||||
"eslint-plugin-prettier": "^3.3.1",
|
"eslint-plugin-prettier": "^3.4.0",
|
||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
"rollup": "^2.44.0",
|
"rollup": "^2.47.0",
|
||||||
"rollup-plugin-json": "^4.0.0",
|
"rollup-plugin-json": "^4.0.0",
|
||||||
"rollup-plugin-node-resolve": "^5.2.0",
|
"rollup-plugin-node-resolve": "^5.2.0",
|
||||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||||
"rollup-plugin-typescript2": "^0.30.0",
|
"rollup-plugin-typescript2": "^0.30.0",
|
||||||
"start-server-and-test": "^1.11.6",
|
"start-server-and-test": "^1.11.6",
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "^9.1.1",
|
||||||
"typescript": "^4.2.3"
|
"typescript": "^4.2.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
|
|
|
@ -14,10 +14,10 @@ test-dump-genesis-accounts = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arrayref = "0.3.6"
|
arrayref = "0.3.6"
|
||||||
|
flux-aggregator = { git = "https://github.com/octopus-network/solana-flux-aggregator", rev = "9cfaec5", features = ["no-entrypoint"] }
|
||||||
num-derive = "0.3"
|
num-derive = "0.3"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
serum_dex = { git = "https://github.com/project-serum/serum-dex", rev = "991a86e", features = ["no-entrypoint"] }
|
solana-program = "1.6.7"
|
||||||
solana-program = "1.6.2"
|
|
||||||
spl-token = { path = "../../token/program", features = [ "no-entrypoint" ] }
|
spl-token = { path = "../../token/program", features = [ "no-entrypoint" ] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
uint = "0.8"
|
uint = "0.8"
|
||||||
|
@ -27,8 +27,8 @@ assert_matches = "1.5.0"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
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"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8"
|
||||||
|
|
||||||
|
|
|
@ -1,345 +0,0 @@
|
||||||
//! Dex market used for simulating trades
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
error::LendingError,
|
|
||||||
math::{Decimal, TryAdd, TryDiv, TryMul, TrySub},
|
|
||||||
state::TokenConverter,
|
|
||||||
};
|
|
||||||
use arrayref::{array_refs, mut_array_refs};
|
|
||||||
use serum_dex::critbit::{Slab, SlabView};
|
|
||||||
use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
|
|
||||||
use std::{cell::RefMut, convert::TryFrom};
|
|
||||||
|
|
||||||
/// Side of the dex market order book
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
|
||||||
enum Side {
|
|
||||||
Bid,
|
|
||||||
Ask,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Market currency
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
|
||||||
enum Currency {
|
|
||||||
Base,
|
|
||||||
Quote,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Currency {
|
|
||||||
fn opposite(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Currency::Base => Currency::Quote,
|
|
||||||
Currency::Quote => Currency::Base,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trade action for trade simulator
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
pub enum TradeAction {
|
|
||||||
/// Sell tokens
|
|
||||||
Sell,
|
|
||||||
/// Buy tokens
|
|
||||||
Buy,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dex market order
|
|
||||||
struct Order {
|
|
||||||
price: u64,
|
|
||||||
quantity: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trade simulator
|
|
||||||
pub struct TradeSimulator<'a> {
|
|
||||||
dex_market: DexMarket,
|
|
||||||
orders: DexMarketOrders<'a>,
|
|
||||||
orders_side: Side,
|
|
||||||
quote_token_mint: &'a Pubkey,
|
|
||||||
buy_token_mint: &'a Pubkey,
|
|
||||||
sell_token_mint: &'a Pubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TokenConverter for TradeSimulator<'a> {
|
|
||||||
fn best_price(&mut self, token_mint: &Pubkey) -> Result<Decimal, ProgramError> {
|
|
||||||
let action = if token_mint == self.buy_token_mint {
|
|
||||||
TradeAction::Buy
|
|
||||||
} else {
|
|
||||||
TradeAction::Sell
|
|
||||||
};
|
|
||||||
|
|
||||||
let currency = if token_mint == self.quote_token_mint {
|
|
||||||
Currency::Quote
|
|
||||||
} else {
|
|
||||||
Currency::Base
|
|
||||||
};
|
|
||||||
|
|
||||||
let order_book_side = match (action, currency) {
|
|
||||||
(TradeAction::Buy, Currency::Base) => Side::Ask,
|
|
||||||
(TradeAction::Sell, Currency::Quote) => Side::Ask,
|
|
||||||
(TradeAction::Buy, Currency::Quote) => Side::Bid,
|
|
||||||
(TradeAction::Sell, Currency::Base) => Side::Bid,
|
|
||||||
};
|
|
||||||
if order_book_side != self.orders_side {
|
|
||||||
return Err(LendingError::DexInvalidOrderBookSide.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let best_order_price = self
|
|
||||||
.orders
|
|
||||||
.best_order_price()
|
|
||||||
.ok_or(LendingError::TradeSimulationError)?;
|
|
||||||
|
|
||||||
let input_token = Decimal::one().try_div(self.dex_market.get_lots(currency))?;
|
|
||||||
let output_token_price = if currency == Currency::Base {
|
|
||||||
input_token.try_mul(best_order_price)
|
|
||||||
} else {
|
|
||||||
input_token.try_div(best_order_price)
|
|
||||||
}?;
|
|
||||||
output_token_price.try_mul(self.dex_market.get_lots(currency.opposite()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert(
|
|
||||||
self,
|
|
||||||
from_amount: Decimal,
|
|
||||||
from_token_mint: &Pubkey,
|
|
||||||
) -> Result<Decimal, ProgramError> {
|
|
||||||
let action = if from_token_mint == self.buy_token_mint {
|
|
||||||
TradeAction::Buy
|
|
||||||
} else {
|
|
||||||
TradeAction::Sell
|
|
||||||
};
|
|
||||||
|
|
||||||
self.simulate_trade(action, from_amount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TradeSimulator<'a> {
|
|
||||||
/// Create a new TradeSimulator
|
|
||||||
pub fn new(
|
|
||||||
dex_market_info: &AccountInfo,
|
|
||||||
dex_market_orders: &AccountInfo,
|
|
||||||
memory: &'a AccountInfo,
|
|
||||||
quote_token_mint: &'a Pubkey,
|
|
||||||
buy_token_mint: &'a Pubkey,
|
|
||||||
sell_token_mint: &'a Pubkey,
|
|
||||||
) -> Result<Self, ProgramError> {
|
|
||||||
let dex_market = DexMarket::new(dex_market_info);
|
|
||||||
let orders = DexMarketOrders::new(&dex_market, dex_market_orders, memory)?;
|
|
||||||
let orders_side = orders.side;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
dex_market,
|
|
||||||
orders,
|
|
||||||
orders_side,
|
|
||||||
quote_token_mint,
|
|
||||||
buy_token_mint,
|
|
||||||
sell_token_mint,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Simulate a trade
|
|
||||||
pub fn simulate_trade(
|
|
||||||
mut self,
|
|
||||||
action: TradeAction,
|
|
||||||
quantity: Decimal,
|
|
||||||
) -> Result<Decimal, ProgramError> {
|
|
||||||
let token_mint = match action {
|
|
||||||
TradeAction::Buy => self.buy_token_mint,
|
|
||||||
TradeAction::Sell => self.sell_token_mint,
|
|
||||||
};
|
|
||||||
|
|
||||||
let currency = if token_mint == self.quote_token_mint {
|
|
||||||
Currency::Quote
|
|
||||||
} else {
|
|
||||||
Currency::Base
|
|
||||||
};
|
|
||||||
|
|
||||||
let order_book_side = match (action, currency) {
|
|
||||||
(TradeAction::Buy, Currency::Base) => Side::Ask,
|
|
||||||
(TradeAction::Sell, Currency::Quote) => Side::Ask,
|
|
||||||
(TradeAction::Buy, Currency::Quote) => Side::Bid,
|
|
||||||
(TradeAction::Sell, Currency::Base) => Side::Bid,
|
|
||||||
};
|
|
||||||
|
|
||||||
if order_book_side != self.orders_side {
|
|
||||||
return Err(LendingError::DexInvalidOrderBookSide.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let input_quantity: Decimal = quantity.try_div(self.dex_market.get_lots(currency))?;
|
|
||||||
let output_quantity = self.exchange_with_order_book(input_quantity, currency)?;
|
|
||||||
output_quantity.try_mul(self.dex_market.get_lots(currency.opposite()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Exchange tokens by filling orders
|
|
||||||
fn exchange_with_order_book(
|
|
||||||
&mut self,
|
|
||||||
mut input_quantity: Decimal,
|
|
||||||
currency: Currency,
|
|
||||||
) -> Result<Decimal, ProgramError> {
|
|
||||||
let mut output_quantity = Decimal::zero();
|
|
||||||
|
|
||||||
let zero = Decimal::zero();
|
|
||||||
while input_quantity > zero {
|
|
||||||
let next_order = self
|
|
||||||
.orders
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| ProgramError::from(LendingError::TradeSimulationError))?;
|
|
||||||
|
|
||||||
let next_order_price = next_order.price;
|
|
||||||
let base_quantity = next_order.quantity;
|
|
||||||
|
|
||||||
let (filled, output) = if currency == Currency::Base {
|
|
||||||
let filled = input_quantity.min(Decimal::from(base_quantity));
|
|
||||||
(filled, filled.try_mul(next_order_price)?)
|
|
||||||
} else {
|
|
||||||
let quote_quantity = Decimal::from(base_quantity).try_mul(next_order_price)?;
|
|
||||||
let filled = input_quantity.min(quote_quantity);
|
|
||||||
(filled, filled.try_div(next_order_price)?)
|
|
||||||
};
|
|
||||||
|
|
||||||
input_quantity = input_quantity.try_sub(filled)?;
|
|
||||||
output_quantity = output_quantity.try_add(output)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(output_quantity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dex market order account info
|
|
||||||
struct DexMarketOrders<'a> {
|
|
||||||
heap: Option<RefMut<'a, Slab>>,
|
|
||||||
side: Side,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DexMarketOrders<'a> {
|
|
||||||
/// Create a new DexMarketOrders
|
|
||||||
fn new(
|
|
||||||
dex_market: &DexMarket,
|
|
||||||
orders: &AccountInfo,
|
|
||||||
memory: &'a AccountInfo,
|
|
||||||
) -> Result<Self, ProgramError> {
|
|
||||||
let side = match orders.key {
|
|
||||||
key if key == &dex_market.bids => Side::Bid,
|
|
||||||
key if key == &dex_market.asks => Side::Ask,
|
|
||||||
_ => return Err(LendingError::DexInvalidOrderBookSide.into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if memory.data_len() < orders.data_len() {
|
|
||||||
return Err(LendingError::MemoryTooSmall.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut memory_data = memory.data.borrow_mut();
|
|
||||||
fast_copy(&orders.data.borrow(), &mut memory_data);
|
|
||||||
let heap = Some(RefMut::map(memory_data, |bytes| {
|
|
||||||
// strip padding and header
|
|
||||||
let start = 5 + 8;
|
|
||||||
let end = bytes.len() - 7;
|
|
||||||
Slab::new(&mut bytes[start..end])
|
|
||||||
}));
|
|
||||||
|
|
||||||
Ok(Self { heap, side })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn best_order_price(&mut self) -> Option<u64> {
|
|
||||||
let side = self.side;
|
|
||||||
self.heap.as_mut().and_then(|heap| {
|
|
||||||
let handle = match side {
|
|
||||||
Side::Bid => heap.find_max(),
|
|
||||||
Side::Ask => heap.find_min(),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
Some(heap.get_mut(handle)?.as_leaf_mut()?.price().get())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for DexMarketOrders<'_> {
|
|
||||||
type Item = Order;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Order> {
|
|
||||||
let leaf_node = match self.side {
|
|
||||||
Side::Bid => self.heap.as_mut().and_then(|heap| heap.remove_max()),
|
|
||||||
Side::Ask => self.heap.as_mut().and_then(|heap| heap.remove_min()),
|
|
||||||
}?;
|
|
||||||
|
|
||||||
Some(Order {
|
|
||||||
price: leaf_node.price().get(),
|
|
||||||
quantity: leaf_node.quantity(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Offset for dex market base mint
|
|
||||||
pub const BASE_MINT_OFFSET: usize = 6;
|
|
||||||
/// Offset for dex market quote mint
|
|
||||||
pub const QUOTE_MINT_OFFSET: usize = 10;
|
|
||||||
|
|
||||||
const BIDS_OFFSET: usize = 35;
|
|
||||||
const ASKS_OFFSET: usize = 39;
|
|
||||||
|
|
||||||
/// Dex market info
|
|
||||||
pub struct DexMarket {
|
|
||||||
bids: Pubkey,
|
|
||||||
asks: Pubkey,
|
|
||||||
base_lots: u64,
|
|
||||||
quote_lots: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DexMarket {
|
|
||||||
/// Create a new DexMarket
|
|
||||||
fn new(dex_market_info: &AccountInfo) -> Self {
|
|
||||||
let dex_market_data = dex_market_info.data.borrow();
|
|
||||||
let bids = Self::pubkey_at_offset(&dex_market_data, BIDS_OFFSET);
|
|
||||||
let asks = Self::pubkey_at_offset(&dex_market_data, ASKS_OFFSET);
|
|
||||||
let base_lots = Self::base_lots(&dex_market_data);
|
|
||||||
let quote_lots = Self::quote_lots(&dex_market_data);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
bids,
|
|
||||||
asks,
|
|
||||||
base_lots,
|
|
||||||
quote_lots,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_lots(&self, currency: Currency) -> u64 {
|
|
||||||
match currency {
|
|
||||||
Currency::Base => self.base_lots,
|
|
||||||
Currency::Quote => self.quote_lots,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn base_lots(data: &[u8]) -> u64 {
|
|
||||||
let count_start = 5 + 43 * 8;
|
|
||||||
let count_end = count_start + 8;
|
|
||||||
u64::from_le_bytes(<[u8; 8]>::try_from(&data[count_start..count_end]).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn quote_lots(data: &[u8]) -> u64 {
|
|
||||||
let count_start = 5 + 44 * 8;
|
|
||||||
let count_end = count_start + 8;
|
|
||||||
u64::from_le_bytes(<[u8; 8]>::try_from(&data[count_start..count_end]).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get pubkey located at offset
|
|
||||||
pub fn pubkey_at_offset(data: &[u8], offset: usize) -> Pubkey {
|
|
||||||
let count_start = 5 + offset * 8;
|
|
||||||
let count_end = count_start + 32;
|
|
||||||
Pubkey::new(&data[count_start..count_end])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A more efficient `copy_from_slice` implementation.
|
|
||||||
fn fast_copy(mut src: &[u8], mut dst: &mut [u8]) {
|
|
||||||
const COPY_SIZE: usize = 512;
|
|
||||||
while src.len() >= COPY_SIZE {
|
|
||||||
#[allow(clippy::ptr_offset_with_cast)]
|
|
||||||
let (src_word, src_rem) = array_refs![src, COPY_SIZE; ..;];
|
|
||||||
#[allow(clippy::ptr_offset_with_cast)]
|
|
||||||
let (dst_word, dst_rem) = mut_array_refs![dst, COPY_SIZE; ..;];
|
|
||||||
*dst_word = *src_word;
|
|
||||||
src = src_rem;
|
|
||||||
dst = dst_rem;
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
std::ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), src.len());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -31,17 +31,20 @@ pub enum LendingError {
|
||||||
/// The owner of the account input isn't set to the correct token program id.
|
/// The owner of the account input isn't set to the correct token program id.
|
||||||
#[error("Input token account is not owned by the correct token program id")]
|
#[error("Input token account is not owned by the correct token program id")]
|
||||||
InvalidTokenOwner,
|
InvalidTokenOwner,
|
||||||
|
/// Expected an SPL Token account
|
||||||
|
#[error("Input token account is not valid")]
|
||||||
|
InvalidTokenAccount,
|
||||||
/// Expected an SPL Token mint
|
/// Expected an SPL Token mint
|
||||||
#[error("Input token mint account is not valid")]
|
#[error("Input token mint account is not valid")]
|
||||||
InvalidTokenMint,
|
InvalidTokenMint,
|
||||||
/// Expected a different SPL Token program
|
/// Expected a different SPL Token program
|
||||||
#[error("Input token program account is not valid")]
|
#[error("Input token program account is not valid")]
|
||||||
InvalidTokenProgram,
|
InvalidTokenProgram,
|
||||||
|
|
||||||
|
// 10
|
||||||
/// Invalid amount, must be greater than zero
|
/// Invalid amount, must be greater than zero
|
||||||
#[error("Input amount is invalid")]
|
#[error("Input amount is invalid")]
|
||||||
InvalidAmount,
|
InvalidAmount,
|
||||||
|
|
||||||
// 10
|
|
||||||
/// Invalid config value
|
/// Invalid config value
|
||||||
#[error("Input config value is invalid")]
|
#[error("Input config value is invalid")]
|
||||||
InvalidConfig,
|
InvalidConfig,
|
||||||
|
@ -54,62 +57,8 @@ pub enum LendingError {
|
||||||
/// Math operation overflow
|
/// Math operation overflow
|
||||||
#[error("Math operation overflow")]
|
#[error("Math operation overflow")]
|
||||||
MathOverflow,
|
MathOverflow,
|
||||||
/// Negative interest rate
|
|
||||||
#[error("Interest rate is negative")]
|
|
||||||
NegativeInterestRate,
|
|
||||||
|
|
||||||
// 15
|
// 15
|
||||||
/// Memory is too small
|
|
||||||
#[error("Memory is too small")]
|
|
||||||
MemoryTooSmall,
|
|
||||||
/// The reserve lending market must be the same
|
|
||||||
#[error("Reserve mints do not match dex market mints")]
|
|
||||||
DexMarketMintMismatch,
|
|
||||||
/// The reserve lending market must be the same
|
|
||||||
#[error("Reserve lending market mismatch")]
|
|
||||||
LendingMarketMismatch,
|
|
||||||
/// The obligation token owner must be the same if reusing an obligation
|
|
||||||
#[error("Obligation token owner mismatch")]
|
|
||||||
ObligationTokenOwnerMismatch,
|
|
||||||
/// Insufficient liquidity available
|
|
||||||
#[error("Insufficient liquidity available")]
|
|
||||||
InsufficientLiquidity,
|
|
||||||
|
|
||||||
// 20
|
|
||||||
/// This reserve's collateral cannot be used for borrows
|
|
||||||
#[error("Input reserve has collateral disabled")]
|
|
||||||
ReserveCollateralDisabled,
|
|
||||||
/// Input reserves cannot be the same
|
|
||||||
#[error("Input reserves cannot be the same")]
|
|
||||||
DuplicateReserve,
|
|
||||||
/// Input reserves cannot use the same liquidity mint
|
|
||||||
#[error("Input reserves cannot use the same liquidity mint")]
|
|
||||||
DuplicateReserveMint,
|
|
||||||
/// Obligation amount is empty
|
|
||||||
#[error("Obligation amount is empty")]
|
|
||||||
ObligationEmpty,
|
|
||||||
/// Cannot liquidate healthy obligations
|
|
||||||
#[error("Cannot liquidate healthy obligations")]
|
|
||||||
HealthyObligation,
|
|
||||||
|
|
||||||
// 25
|
|
||||||
/// Borrow amount too small
|
|
||||||
#[error("Borrow amount too small")]
|
|
||||||
BorrowTooSmall,
|
|
||||||
/// Liquidation amount too small
|
|
||||||
#[error("Liquidation amount too small to receive collateral")]
|
|
||||||
LiquidationTooSmall,
|
|
||||||
/// Reserve state stale
|
|
||||||
#[error("Reserve state needs to be updated for the current slot")]
|
|
||||||
ReserveStale,
|
|
||||||
/// Trade simulation error
|
|
||||||
#[error("Trade simulation error")]
|
|
||||||
TradeSimulationError,
|
|
||||||
/// Invalid dex order book side
|
|
||||||
#[error("Invalid dex order book side")]
|
|
||||||
DexInvalidOrderBookSide,
|
|
||||||
|
|
||||||
// 30
|
|
||||||
/// Token initialize mint failed
|
/// Token initialize mint failed
|
||||||
#[error("Token initialize mint failed")]
|
#[error("Token initialize mint failed")]
|
||||||
TokenInitializeMintFailed,
|
TokenInitializeMintFailed,
|
||||||
|
@ -126,16 +75,87 @@ pub enum LendingError {
|
||||||
#[error("Token burn failed")]
|
#[error("Token burn failed")]
|
||||||
TokenBurnFailed,
|
TokenBurnFailed,
|
||||||
|
|
||||||
|
// 20
|
||||||
|
/// Insufficient liquidity available
|
||||||
|
#[error("Insufficient liquidity available")]
|
||||||
|
InsufficientLiquidity,
|
||||||
|
/// This reserve's collateral cannot be used for borrows
|
||||||
|
#[error("Input reserve has collateral disabled")]
|
||||||
|
ReserveCollateralDisabled,
|
||||||
|
/// Reserve state stale
|
||||||
|
#[error("Reserve state needs to be refreshed")]
|
||||||
|
ReserveStale,
|
||||||
|
/// Withdraw amount too small
|
||||||
|
#[error("Withdraw amount too small")]
|
||||||
|
WithdrawTooSmall,
|
||||||
|
/// Withdraw amount too large
|
||||||
|
#[error("Withdraw amount too large")]
|
||||||
|
WithdrawTooLarge,
|
||||||
|
|
||||||
|
// 25
|
||||||
|
/// Borrow amount too small
|
||||||
|
#[error("Borrow amount too small to receive liquidity after fees")]
|
||||||
|
BorrowTooSmall,
|
||||||
|
/// Borrow amount too large
|
||||||
|
#[error("Borrow amount too large for deposited collateral")]
|
||||||
|
BorrowTooLarge,
|
||||||
|
/// Repay amount too small
|
||||||
|
#[error("Repay amount too small to transfer liquidity")]
|
||||||
|
RepayTooSmall,
|
||||||
|
/// Liquidation amount too small
|
||||||
|
#[error("Liquidation amount too small to receive collateral")]
|
||||||
|
LiquidationTooSmall,
|
||||||
|
/// Cannot liquidate healthy obligations
|
||||||
|
#[error("Cannot liquidate healthy obligations")]
|
||||||
|
ObligationHealthy,
|
||||||
|
|
||||||
|
// 30
|
||||||
|
/// Obligation state stale
|
||||||
|
#[error("Obligation state needs to be refreshed")]
|
||||||
|
ObligationStale,
|
||||||
|
/// Obligation reserve limit exceeded
|
||||||
|
#[error("Obligation reserve limit exceeded")]
|
||||||
|
ObligationReserveLimit,
|
||||||
|
/// Expected a different obligation owner
|
||||||
|
#[error("Obligation owner is invalid")]
|
||||||
|
InvalidObligationOwner,
|
||||||
|
/// Obligation deposits are empty
|
||||||
|
#[error("Obligation deposits are empty")]
|
||||||
|
ObligationDepositsEmpty,
|
||||||
|
/// Obligation borrows are empty
|
||||||
|
#[error("Obligation borrows are empty")]
|
||||||
|
ObligationBorrowsEmpty,
|
||||||
|
|
||||||
// 35
|
// 35
|
||||||
/// Invalid obligation collateral amount
|
/// Obligation deposits have zero value
|
||||||
#[error("Invalid obligation collateral amount")]
|
#[error("Obligation deposits have zero value")]
|
||||||
|
ObligationDepositsZero,
|
||||||
|
/// Obligation borrows have zero value
|
||||||
|
#[error("Obligation borrows have zero value")]
|
||||||
|
ObligationBorrowsZero,
|
||||||
|
/// Invalid obligation collateral
|
||||||
|
#[error("Invalid obligation collateral")]
|
||||||
InvalidObligationCollateral,
|
InvalidObligationCollateral,
|
||||||
/// Obligation collateral is already below required amount
|
/// Invalid obligation liquidity
|
||||||
#[error("Obligation collateral is already below required amount")]
|
#[error("Invalid obligation liquidity")]
|
||||||
ObligationCollateralBelowRequired,
|
InvalidObligationLiquidity,
|
||||||
/// Obligation collateral cannot be withdrawn below required amount
|
/// Obligation collateral is empty
|
||||||
#[error("Obligation collateral cannot be withdrawn below required amount")]
|
#[error("Obligation collateral is empty")]
|
||||||
ObligationCollateralWithdrawBelowRequired,
|
ObligationCollateralEmpty,
|
||||||
|
|
||||||
|
// 40
|
||||||
|
/// Obligation liquidity is empty
|
||||||
|
#[error("Obligation liquidity is empty")]
|
||||||
|
ObligationLiquidityEmpty,
|
||||||
|
/// Negative interest rate
|
||||||
|
#[error("Interest rate is negative")]
|
||||||
|
NegativeInterestRate,
|
||||||
|
/// Oracle config is invalid
|
||||||
|
#[error("Input oracle config is invalid")]
|
||||||
|
InvalidOracleConfig,
|
||||||
|
/// Not enough liquidity after flash loan
|
||||||
|
#[error("Not enough liquidity after flash loan")]
|
||||||
|
NotEnoughLiquidityAfterFlashLoan,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LendingError> for ProgramError {
|
impl From<LendingError> for ProgramError {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
//! A lending program for the Solana blockchain.
|
//! A lending program for the Solana blockchain.
|
||||||
|
|
||||||
pub mod dex_market;
|
|
||||||
pub mod entrypoint;
|
pub mod entrypoint;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod instruction;
|
pub mod instruction;
|
||||||
|
|
|
@ -12,11 +12,12 @@
|
||||||
#![allow(clippy::ptr_offset_with_cast)]
|
#![allow(clippy::ptr_offset_with_cast)]
|
||||||
#![allow(clippy::manual_range_contains)]
|
#![allow(clippy::manual_range_contains)]
|
||||||
|
|
||||||
use crate::math::Rate;
|
use crate::{
|
||||||
use crate::{error::LendingError, math::common::*};
|
error::LendingError,
|
||||||
|
math::{common::*, Rate},
|
||||||
|
};
|
||||||
use solana_program::program_error::ProgramError;
|
use solana_program::program_error::ProgramError;
|
||||||
use std::convert::TryFrom;
|
use std::{convert::TryFrom, fmt};
|
||||||
use std::fmt;
|
|
||||||
use uint::construct_uint;
|
use uint::construct_uint;
|
||||||
|
|
||||||
// U192 with 192 bits consisting of 3 x 64-bit words
|
// U192 with 192 bits consisting of 3 x 64-bit words
|
||||||
|
@ -55,6 +56,7 @@ impl Decimal {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return raw scaled value if it fits within u128
|
/// Return raw scaled value if it fits within u128
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
pub fn to_scaled_val(&self) -> Result<u128, ProgramError> {
|
pub fn to_scaled_val(&self) -> Result<u128, ProgramError> {
|
||||||
Ok(u128::try_from(self.0).map_err(|_| LendingError::MathOverflow)?)
|
Ok(u128::try_from(self.0).map_err(|_| LendingError::MathOverflow)?)
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ impl Rate {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return raw scaled value
|
/// Return raw scaled value
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
pub fn to_scaled_val(&self) -> u128 {
|
pub fn to_scaled_val(&self) -> u128 {
|
||||||
self.0.as_u128()
|
self.0.as_u128()
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,58 @@
|
||||||
|
use crate::error::LendingError;
|
||||||
|
use solana_program::{clock::Slot, program_error::ProgramError};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
/// Number of slots to consider stale after
|
||||||
|
pub const STALE_AFTER_SLOTS_ELAPSED: u64 = 1;
|
||||||
|
|
||||||
|
/// Last update state
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct LastUpdate {
|
||||||
|
/// Last slot when updated
|
||||||
|
pub slot: Slot,
|
||||||
|
/// True when marked stale, false when slot updated
|
||||||
|
pub stale: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LastUpdate {
|
||||||
|
/// Create new last update
|
||||||
|
pub fn new(slot: Slot) -> Self {
|
||||||
|
Self { slot, stale: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return slots elapsed since given slot
|
||||||
|
pub fn slots_elapsed(&self, slot: Slot) -> Result<u64, ProgramError> {
|
||||||
|
let slots_elapsed = slot
|
||||||
|
.checked_sub(self.slot)
|
||||||
|
.ok_or(LendingError::MathOverflow)?;
|
||||||
|
Ok(slots_elapsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set last update slot
|
||||||
|
pub fn update_slot(&mut self, slot: Slot) {
|
||||||
|
self.slot = slot;
|
||||||
|
self.stale = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set stale to true
|
||||||
|
pub fn mark_stale(&mut self) {
|
||||||
|
self.stale = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if marked stale or last update slot is too long ago
|
||||||
|
pub fn is_stale(&self, slot: Slot) -> Result<bool, ProgramError> {
|
||||||
|
Ok(self.stale || self.slots_elapsed(slot)? >= STALE_AFTER_SLOTS_ELAPSED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for LastUpdate {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.slot == other.slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for LastUpdate {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
self.slot.partial_cmp(&other.slot)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
|
use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
|
||||||
use solana_program::{
|
use solana_program::{
|
||||||
|
msg,
|
||||||
program_error::ProgramError,
|
program_error::ProgramError,
|
||||||
program_pack::{IsInitialized, Pack, Sealed},
|
program_pack::{IsInitialized, Pack, Sealed},
|
||||||
pubkey::Pubkey,
|
pubkey::{Pubkey, PUBKEY_BYTES},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Lending market state
|
/// Lending market state
|
||||||
|
@ -21,6 +22,36 @@ pub struct LendingMarket {
|
||||||
pub token_program_id: Pubkey,
|
pub token_program_id: Pubkey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LendingMarket {
|
||||||
|
/// Create a new lending market
|
||||||
|
pub fn new(params: InitLendingMarketParams) -> Self {
|
||||||
|
let mut lending_market = Self::default();
|
||||||
|
Self::init(&mut lending_market, params);
|
||||||
|
lending_market
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize a lending market
|
||||||
|
pub fn init(&mut self, params: InitLendingMarketParams) {
|
||||||
|
self.version = PROGRAM_VERSION;
|
||||||
|
self.bump_seed = params.bump_seed;
|
||||||
|
self.token_program_id = params.token_program_id;
|
||||||
|
self.quote_token_mint = params.quote_token_mint;
|
||||||
|
self.owner = params.owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize a lending market
|
||||||
|
pub struct InitLendingMarketParams {
|
||||||
|
/// Bump seed for derived authority address
|
||||||
|
pub bump_seed: u8,
|
||||||
|
/// Owner authority which can add new reserves
|
||||||
|
pub owner: Pubkey,
|
||||||
|
/// Quote currency token mint
|
||||||
|
pub quote_token_mint: Pubkey,
|
||||||
|
/// Token program id
|
||||||
|
pub token_program_id: Pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
impl Sealed for LendingMarket {}
|
impl Sealed for LendingMarket {}
|
||||||
impl IsInitialized for LendingMarket {
|
impl IsInitialized for LendingMarket {
|
||||||
fn is_initialized(&self) -> bool {
|
fn is_initialized(&self) -> bool {
|
||||||
|
@ -28,18 +59,33 @@ impl IsInitialized for LendingMarket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const LENDING_MARKET_LEN: usize = 160;
|
const LENDING_MARKET_LEN: usize = 226; // 1 + 1 + 32 + 32 + 32 + 128
|
||||||
impl Pack for LendingMarket {
|
impl Pack for LendingMarket {
|
||||||
const LEN: usize = 160;
|
const LEN: usize = LENDING_MARKET_LEN;
|
||||||
|
|
||||||
/// Unpacks a byte buffer into a [LendingMarketInfo](struct.LendingMarketInfo.html).
|
fn pack_into_slice(&self, output: &mut [u8]) {
|
||||||
|
let output = array_mut_ref![output, 0, LENDING_MARKET_LEN];
|
||||||
|
#[allow(clippy::ptr_offset_with_cast)]
|
||||||
|
let (version, bump_seed, owner, quote_token_mint, token_program_id, _padding) =
|
||||||
|
mut_array_refs![output, 1, 1, PUBKEY_BYTES, PUBKEY_BYTES, PUBKEY_BYTES, 128];
|
||||||
|
|
||||||
|
*version = self.version.to_le_bytes();
|
||||||
|
*bump_seed = self.bump_seed.to_le_bytes();
|
||||||
|
owner.copy_from_slice(self.owner.as_ref());
|
||||||
|
quote_token_mint.copy_from_slice(self.quote_token_mint.as_ref());
|
||||||
|
token_program_id.copy_from_slice(self.token_program_id.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unpacks a byte buffer into a [LendingMarketInfo](struct.LendingMarketInfo.html)
|
||||||
fn unpack_from_slice(input: &[u8]) -> Result<Self, ProgramError> {
|
fn unpack_from_slice(input: &[u8]) -> Result<Self, ProgramError> {
|
||||||
let input = array_ref![input, 0, LENDING_MARKET_LEN];
|
let input = array_ref![input, 0, LENDING_MARKET_LEN];
|
||||||
#[allow(clippy::ptr_offset_with_cast)]
|
#[allow(clippy::ptr_offset_with_cast)]
|
||||||
let (version, bump_seed, owner, quote_token_mint, token_program_id, _padding) =
|
let (version, bump_seed, owner, quote_token_mint, token_program_id, _padding) =
|
||||||
array_refs![input, 1, 1, 32, 32, 32, 62];
|
array_refs![input, 1, 1, PUBKEY_BYTES, PUBKEY_BYTES, PUBKEY_BYTES, 128];
|
||||||
|
|
||||||
let version = u8::from_le_bytes(*version);
|
let version = u8::from_le_bytes(*version);
|
||||||
if version > PROGRAM_VERSION {
|
if version > PROGRAM_VERSION {
|
||||||
|
msg!("Lending market version does not match lending program version");
|
||||||
return Err(ProgramError::InvalidAccountData);
|
return Err(ProgramError::InvalidAccountData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,16 +97,4 @@ impl Pack for LendingMarket {
|
||||||
token_program_id: Pubkey::new_from_array(*token_program_id),
|
token_program_id: Pubkey::new_from_array(*token_program_id),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack_into_slice(&self, output: &mut [u8]) {
|
|
||||||
let output = array_mut_ref![output, 0, LENDING_MARKET_LEN];
|
|
||||||
#[allow(clippy::ptr_offset_with_cast)]
|
|
||||||
let (version, bump_seed, owner, quote_token_mint, token_program_id, _padding) =
|
|
||||||
mut_array_refs![output, 1, 1, 32, 32, 32, 62];
|
|
||||||
*version = self.version.to_le_bytes();
|
|
||||||
*bump_seed = self.bump_seed.to_le_bytes();
|
|
||||||
owner.copy_from_slice(self.owner.as_ref());
|
|
||||||
quote_token_mint.copy_from_slice(self.quote_token_mint.as_ref());
|
|
||||||
token_program_id.copy_from_slice(self.token_program_id.as_ref());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
//! State types
|
//! State types
|
||||||
|
|
||||||
|
mod last_update;
|
||||||
mod lending_market;
|
mod lending_market;
|
||||||
mod obligation;
|
mod obligation;
|
||||||
mod reserve;
|
mod reserve;
|
||||||
|
|
||||||
|
pub use last_update::*;
|
||||||
pub use lending_market::*;
|
pub use lending_market::*;
|
||||||
pub use obligation::*;
|
pub use obligation::*;
|
||||||
pub use reserve::*;
|
pub use reserve::*;
|
||||||
|
@ -12,13 +14,15 @@ use crate::math::{Decimal, WAD};
|
||||||
use arrayref::{array_refs, mut_array_refs};
|
use arrayref::{array_refs, mut_array_refs};
|
||||||
use solana_program::{
|
use solana_program::{
|
||||||
clock::{DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT, SECONDS_PER_DAY},
|
clock::{DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT, SECONDS_PER_DAY},
|
||||||
|
msg,
|
||||||
program_error::ProgramError,
|
program_error::ProgramError,
|
||||||
program_option::COption,
|
program_option::COption,
|
||||||
pubkey::Pubkey,
|
pubkey::{Pubkey, PUBKEY_BYTES},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Collateral tokens are initially valued at a ratio of 5:1 (collateral:liquidity)
|
/// Collateral tokens are initially valued at a ratio of 5:1 (collateral:liquidity)
|
||||||
pub const INITIAL_COLLATERAL_RATIO: u64 = 5;
|
// @FIXME: restore to 5
|
||||||
|
pub const INITIAL_COLLATERAL_RATIO: u64 = 1;
|
||||||
const INITIAL_COLLATERAL_RATE: u64 = INITIAL_COLLATERAL_RATIO * WAD;
|
const INITIAL_COLLATERAL_RATE: u64 = INITIAL_COLLATERAL_RATIO * WAD;
|
||||||
|
|
||||||
/// Current version of the program and all new accounts created
|
/// Current version of the program and all new accounts created
|
||||||
|
@ -32,22 +36,10 @@ pub const UNINITIALIZED_VERSION: u8 = 0;
|
||||||
pub const SLOTS_PER_YEAR: u64 =
|
pub const SLOTS_PER_YEAR: u64 =
|
||||||
DEFAULT_TICKS_PER_SECOND / DEFAULT_TICKS_PER_SLOT * SECONDS_PER_DAY * 365;
|
DEFAULT_TICKS_PER_SECOND / DEFAULT_TICKS_PER_SLOT * SECONDS_PER_DAY * 365;
|
||||||
|
|
||||||
/// Token converter
|
|
||||||
pub trait TokenConverter {
|
|
||||||
/// Return best price for specified token
|
|
||||||
fn best_price(&mut self, token_mint: &Pubkey) -> Result<Decimal, ProgramError>;
|
|
||||||
|
|
||||||
/// Convert between two different tokens
|
|
||||||
fn convert(
|
|
||||||
self,
|
|
||||||
from_amount: Decimal,
|
|
||||||
from_token_mint: &Pubkey,
|
|
||||||
) -> Result<Decimal, ProgramError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
fn pack_coption_key(src: &COption<Pubkey>, dst: &mut [u8; 36]) {
|
fn pack_coption_key(src: &COption<Pubkey>, dst: &mut [u8; 4 + PUBKEY_BYTES]) {
|
||||||
let (tag, body) = mut_array_refs![dst, 4, 32];
|
#[allow(clippy::ptr_offset_with_cast)]
|
||||||
|
let (tag, body) = mut_array_refs![dst, 4, PUBKEY_BYTES];
|
||||||
match src {
|
match src {
|
||||||
COption::Some(key) => {
|
COption::Some(key) => {
|
||||||
*tag = [1, 0, 0, 0];
|
*tag = [1, 0, 0, 0];
|
||||||
|
@ -59,19 +51,23 @@ fn pack_coption_key(src: &COption<Pubkey>, dst: &mut [u8; 36]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unpack_coption_key(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
|
fn unpack_coption_key(src: &[u8; 4 + PUBKEY_BYTES]) -> Result<COption<Pubkey>, ProgramError> {
|
||||||
let (tag, body) = array_refs![src, 4, 32];
|
#[allow(clippy::ptr_offset_with_cast)]
|
||||||
|
let (tag, body) = array_refs![src, 4, PUBKEY_BYTES];
|
||||||
match *tag {
|
match *tag {
|
||||||
[0, 0, 0, 0] => Ok(COption::None),
|
[0, 0, 0, 0] => Ok(COption::None),
|
||||||
[1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
|
[1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
|
||||||
_ => Err(ProgramError::InvalidAccountData),
|
_ => {
|
||||||
|
msg!("COption<Pubkey> cannot be unpacked");
|
||||||
|
Err(ProgramError::InvalidAccountData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack_decimal(decimal: Decimal, dst: &mut [u8; 16]) {
|
fn pack_decimal(decimal: Decimal, dst: &mut [u8; 16]) {
|
||||||
*dst = decimal
|
*dst = decimal
|
||||||
.to_scaled_val()
|
.to_scaled_val()
|
||||||
.expect("could not pack decimal")
|
.expect("Decimal cannot be packed")
|
||||||
.to_le_bytes();
|
.to_le_bytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +75,21 @@ fn unpack_decimal(src: &[u8; 16]) -> Decimal {
|
||||||
Decimal::from_scaled_val(u128::from_le_bytes(*src))
|
Decimal::from_scaled_val(u128::from_le_bytes(*src))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pack_bool(boolean: bool, dst: &mut [u8; 1]) {
|
||||||
|
*dst = (boolean as u8).to_le_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpack_bool(src: &[u8; 1]) -> Result<bool, ProgramError> {
|
||||||
|
match u8::from_le_bytes(*src) {
|
||||||
|
0 => Ok(false),
|
||||||
|
1 => Ok(true),
|
||||||
|
_ => {
|
||||||
|
msg!("Boolean cannot be unpacked");
|
||||||
|
Err(ProgramError::InvalidAccountData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,185 +1,220 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
error::LendingError,
|
error::LendingError,
|
||||||
math::{Decimal, Rate, TryDiv, TryMul, TrySub},
|
math::{Decimal, Rate, TryAdd, TryDiv, TryMul, TrySub},
|
||||||
};
|
};
|
||||||
use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
|
use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
|
||||||
use solana_program::{
|
use solana_program::{
|
||||||
|
clock::Slot,
|
||||||
entrypoint::ProgramResult,
|
entrypoint::ProgramResult,
|
||||||
|
msg,
|
||||||
program_error::ProgramError,
|
program_error::ProgramError,
|
||||||
program_pack::{IsInitialized, Pack, Sealed},
|
program_pack::{IsInitialized, Pack, Sealed},
|
||||||
pubkey::Pubkey,
|
pubkey::{Pubkey, PUBKEY_BYTES},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
convert::{TryFrom, TryInto},
|
||||||
};
|
};
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
/// Borrow obligation state
|
/// Max number of collateral and liquidity reserve accounts combined for an obligation
|
||||||
|
pub const MAX_OBLIGATION_RESERVES: usize = 10;
|
||||||
|
|
||||||
|
/// Lending market obligation state
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct Obligation {
|
pub struct Obligation {
|
||||||
/// Version of the obligation
|
/// Version of the struct
|
||||||
pub version: u8,
|
pub version: u8,
|
||||||
/// Amount of collateral tokens deposited for this obligation
|
/// Last update to collateral, liquidity, or their market values
|
||||||
pub deposited_collateral_tokens: u64,
|
pub last_update: LastUpdate,
|
||||||
/// Reserve which collateral tokens were deposited into
|
/// Lending market address
|
||||||
pub collateral_reserve: Pubkey,
|
pub lending_market: Pubkey,
|
||||||
/// Borrow rate used for calculating interest.
|
/// Owner authority which can borrow liquidity
|
||||||
pub cumulative_borrow_rate_wads: Decimal,
|
pub owner: Pubkey,
|
||||||
/// Amount of tokens borrowed for this obligation plus interest
|
/// Deposited collateral for the obligation, unique by deposit reserve address
|
||||||
pub borrowed_liquidity_wads: Decimal,
|
pub deposits: Vec<ObligationCollateral>,
|
||||||
/// Reserve which tokens were borrowed from
|
/// Borrowed liquidity for the obligation, unique by borrow reserve address
|
||||||
pub borrow_reserve: Pubkey,
|
pub borrows: Vec<ObligationLiquidity>,
|
||||||
/// Mint address of the tokens for this obligation
|
/// Market value of deposits
|
||||||
pub token_mint: Pubkey,
|
pub deposited_value: Decimal,
|
||||||
|
/// Market value of borrows
|
||||||
|
pub borrowed_value: Decimal,
|
||||||
|
/// The maximum borrow value at the weighted average loan to value ratio
|
||||||
|
pub allowed_borrow_value: Decimal,
|
||||||
|
/// The dangerous borrow value at the weighted average liquidation threshold
|
||||||
|
pub unhealthy_borrow_value: Decimal,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Obligation {
|
impl Obligation {
|
||||||
/// Create new obligation
|
/// Create a new obligation
|
||||||
pub fn new(params: NewObligationParams) -> Self {
|
pub fn new(params: InitObligationParams) -> Self {
|
||||||
let NewObligationParams {
|
let mut obligation = Self::default();
|
||||||
collateral_reserve,
|
Self::init(&mut obligation, params);
|
||||||
borrow_reserve,
|
obligation
|
||||||
token_mint,
|
|
||||||
cumulative_borrow_rate_wads,
|
|
||||||
} = params;
|
|
||||||
|
|
||||||
Self {
|
|
||||||
version: PROGRAM_VERSION,
|
|
||||||
deposited_collateral_tokens: 0,
|
|
||||||
collateral_reserve,
|
|
||||||
cumulative_borrow_rate_wads,
|
|
||||||
borrowed_liquidity_wads: Decimal::zero(),
|
|
||||||
borrow_reserve,
|
|
||||||
token_mint,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maximum amount of loan that can be closed out by a liquidator due
|
/// Initialize an obligation
|
||||||
/// to the remaining balance being too small to be liquidated normally.
|
pub fn init(&mut self, params: InitObligationParams) {
|
||||||
pub fn max_closeable_amount(&self) -> Result<u64, ProgramError> {
|
self.version = PROGRAM_VERSION;
|
||||||
if self.borrowed_liquidity_wads < Decimal::from(CLOSEABLE_AMOUNT) {
|
self.last_update = LastUpdate::new(params.current_slot);
|
||||||
self.borrowed_liquidity_wads.try_ceil_u64()
|
self.lending_market = params.lending_market;
|
||||||
|
self.owner = params.owner;
|
||||||
|
self.deposits = params.deposits;
|
||||||
|
self.borrows = params.borrows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the current ratio of borrowed value to deposited value
|
||||||
|
pub fn loan_to_value(&self) -> Result<Decimal, ProgramError> {
|
||||||
|
self.borrowed_value.try_div(self.deposited_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Repay liquidity and remove it from borrows if zeroed out
|
||||||
|
pub fn repay(&mut self, settle_amount: Decimal, liquidity_index: usize) -> ProgramResult {
|
||||||
|
let liquidity = &mut self.borrows[liquidity_index];
|
||||||
|
if settle_amount == liquidity.borrowed_amount_wads {
|
||||||
|
self.borrows.remove(liquidity_index);
|
||||||
} else {
|
} else {
|
||||||
Ok(0)
|
liquidity.repay(settle_amount)?;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maximum amount of loan that can be repaid by liquidators
|
/// Withdraw collateral and remove it from deposits if zeroed out
|
||||||
pub fn max_liquidation_amount(&self) -> Result<u64, ProgramError> {
|
pub fn withdraw(&mut self, withdraw_amount: u64, collateral_index: usize) -> ProgramResult {
|
||||||
self.borrowed_liquidity_wads
|
let collateral = &mut self.deposits[collateral_index];
|
||||||
.try_mul(Rate::from_percent(LIQUIDATION_CLOSE_FACTOR))?
|
if withdraw_amount == collateral.deposited_amount {
|
||||||
.try_floor_u64()
|
self.deposits.remove(collateral_index);
|
||||||
|
} else {
|
||||||
|
collateral.withdraw(withdraw_amount)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ratio of loan balance to collateral value
|
/// Calculate the maximum collateral value that can be withdrawn
|
||||||
pub fn loan_to_value(
|
pub fn max_withdraw_value(&self) -> Result<Decimal, ProgramError> {
|
||||||
|
let required_deposit_value = self
|
||||||
|
.borrowed_value
|
||||||
|
.try_mul(self.deposited_value)?
|
||||||
|
.try_div(self.allowed_borrow_value)?;
|
||||||
|
if required_deposit_value >= self.deposited_value {
|
||||||
|
return Ok(Decimal::zero());
|
||||||
|
}
|
||||||
|
self.deposited_value.try_sub(required_deposit_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the maximum liquidity value that can be borrowed
|
||||||
|
pub fn remaining_borrow_value(&self) -> Result<Decimal, ProgramError> {
|
||||||
|
self.allowed_borrow_value.try_sub(self.borrowed_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the maximum liquidation amount for a given liquidity
|
||||||
|
pub fn max_liquidation_amount(
|
||||||
&self,
|
&self,
|
||||||
collateral_exchange_rate: CollateralExchangeRate,
|
liquidity: &ObligationLiquidity,
|
||||||
borrow_token_price: Decimal,
|
|
||||||
) -> Result<Decimal, ProgramError> {
|
) -> Result<Decimal, ProgramError> {
|
||||||
let loan = self.borrowed_liquidity_wads;
|
let max_liquidation_value = self
|
||||||
let collateral_value = collateral_exchange_rate
|
.borrowed_value
|
||||||
.decimal_collateral_to_liquidity(self.deposited_collateral_tokens.into())?
|
.try_mul(Rate::from_percent(LIQUIDATION_CLOSE_FACTOR))?
|
||||||
.try_div(borrow_token_price)?;
|
.min(liquidity.market_value);
|
||||||
loan.try_div(collateral_value)
|
let max_liquidation_pct = max_liquidation_value.try_div(liquidity.market_value)?;
|
||||||
|
liquidity.borrowed_amount_wads.try_mul(max_liquidation_pct)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Amount of obligation tokens for given collateral
|
/// Find collateral by deposit reserve
|
||||||
pub fn collateral_to_obligation_token_amount(
|
pub fn find_collateral_in_deposits(
|
||||||
&self,
|
&self,
|
||||||
collateral_amount: u64,
|
deposit_reserve: Pubkey,
|
||||||
obligation_token_supply: u64,
|
) -> Result<(&ObligationCollateral, usize), ProgramError> {
|
||||||
) -> Result<u64, ProgramError> {
|
if self.deposits.is_empty() {
|
||||||
let withdraw_pct =
|
msg!("Obligation has no deposits");
|
||||||
Decimal::from(collateral_amount).try_div(self.deposited_collateral_tokens)?;
|
return Err(LendingError::ObligationDepositsEmpty.into());
|
||||||
let token_amount: Decimal = withdraw_pct.try_mul(obligation_token_supply)?;
|
|
||||||
token_amount.try_floor_u64()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Accrue interest
|
|
||||||
pub fn accrue_interest(&mut self, cumulative_borrow_rate: Decimal) -> ProgramResult {
|
|
||||||
if cumulative_borrow_rate < self.cumulative_borrow_rate_wads {
|
|
||||||
return Err(LendingError::NegativeInterestRate.into());
|
|
||||||
}
|
}
|
||||||
|
let collateral_index = self
|
||||||
let compounded_interest_rate: Rate = cumulative_borrow_rate
|
._find_collateral_index_in_deposits(deposit_reserve)
|
||||||
.try_div(self.cumulative_borrow_rate_wads)?
|
.ok_or(LendingError::InvalidObligationCollateral)?;
|
||||||
.try_into()?;
|
Ok((&self.deposits[collateral_index], collateral_index))
|
||||||
|
|
||||||
self.borrowed_liquidity_wads = self
|
|
||||||
.borrowed_liquidity_wads
|
|
||||||
.try_mul(compounded_interest_rate)?;
|
|
||||||
|
|
||||||
self.cumulative_borrow_rate_wads = cumulative_borrow_rate;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Liquidate part of obligation
|
/// Find or add collateral by deposit reserve
|
||||||
pub fn liquidate(&mut self, repay_amount: Decimal, withdraw_amount: u64) -> ProgramResult {
|
pub fn find_or_add_collateral_to_deposits(
|
||||||
self.borrowed_liquidity_wads = self.borrowed_liquidity_wads.try_sub(repay_amount)?;
|
|
||||||
self.deposited_collateral_tokens = self
|
|
||||||
.deposited_collateral_tokens
|
|
||||||
.checked_sub(withdraw_amount)
|
|
||||||
.ok_or(LendingError::MathOverflow)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Repay borrowed tokens
|
|
||||||
pub fn repay(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
liquidity_amount: u64,
|
deposit_reserve: Pubkey,
|
||||||
obligation_token_supply: u64,
|
) -> Result<&mut ObligationCollateral, ProgramError> {
|
||||||
) -> Result<RepayResult, ProgramError> {
|
if let Some(collateral_index) = self._find_collateral_index_in_deposits(deposit_reserve) {
|
||||||
let decimal_repay_amount =
|
return Ok(&mut self.deposits[collateral_index]);
|
||||||
Decimal::from(liquidity_amount).min(self.borrowed_liquidity_wads);
|
|
||||||
let integer_repay_amount = decimal_repay_amount.try_ceil_u64()?;
|
|
||||||
if integer_repay_amount == 0 {
|
|
||||||
return Err(LendingError::ObligationEmpty.into());
|
|
||||||
}
|
}
|
||||||
|
if self.deposits.len() + self.borrows.len() >= MAX_OBLIGATION_RESERVES {
|
||||||
|
msg!(
|
||||||
|
"Obligation cannot have more than {} deposits and borrows combined",
|
||||||
|
MAX_OBLIGATION_RESERVES
|
||||||
|
);
|
||||||
|
return Err(LendingError::ObligationReserveLimit.into());
|
||||||
|
}
|
||||||
|
let collateral = ObligationCollateral::new(deposit_reserve);
|
||||||
|
self.deposits.push(collateral);
|
||||||
|
Ok(self.deposits.last_mut().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
let repay_pct: Decimal = decimal_repay_amount.try_div(self.borrowed_liquidity_wads)?;
|
fn _find_collateral_index_in_deposits(&self, deposit_reserve: Pubkey) -> Option<usize> {
|
||||||
let collateral_withdraw_amount = {
|
self.deposits
|
||||||
let withdraw_amount: Decimal = repay_pct.try_mul(self.deposited_collateral_tokens)?;
|
.iter()
|
||||||
withdraw_amount.try_floor_u64()?
|
.position(|collateral| collateral.deposit_reserve == deposit_reserve)
|
||||||
};
|
}
|
||||||
|
|
||||||
let obligation_token_amount = self.collateral_to_obligation_token_amount(
|
/// Find liquidity by borrow reserve
|
||||||
collateral_withdraw_amount,
|
pub fn find_liquidity_in_borrows(
|
||||||
obligation_token_supply,
|
&self,
|
||||||
)?;
|
borrow_reserve: Pubkey,
|
||||||
|
) -> Result<(&ObligationLiquidity, usize), ProgramError> {
|
||||||
|
if self.borrows.is_empty() {
|
||||||
|
msg!("Obligation has no borrows");
|
||||||
|
return Err(LendingError::ObligationBorrowsEmpty.into());
|
||||||
|
}
|
||||||
|
let liquidity_index = self
|
||||||
|
._find_liquidity_index_in_borrows(borrow_reserve)
|
||||||
|
.ok_or(LendingError::InvalidObligationLiquidity)?;
|
||||||
|
Ok((&self.borrows[liquidity_index], liquidity_index))
|
||||||
|
}
|
||||||
|
|
||||||
self.liquidate(decimal_repay_amount, collateral_withdraw_amount)?;
|
/// Find or add liquidity by borrow reserve
|
||||||
|
pub fn find_or_add_liquidity_to_borrows(
|
||||||
|
&mut self,
|
||||||
|
borrow_reserve: Pubkey,
|
||||||
|
) -> Result<&mut ObligationLiquidity, ProgramError> {
|
||||||
|
if let Some(liquidity_index) = self._find_liquidity_index_in_borrows(borrow_reserve) {
|
||||||
|
return Ok(&mut self.borrows[liquidity_index]);
|
||||||
|
}
|
||||||
|
if self.deposits.len() + self.borrows.len() >= MAX_OBLIGATION_RESERVES {
|
||||||
|
msg!(
|
||||||
|
"Obligation cannot have more than {} deposits and borrows combined",
|
||||||
|
MAX_OBLIGATION_RESERVES
|
||||||
|
);
|
||||||
|
return Err(LendingError::ObligationReserveLimit.into());
|
||||||
|
}
|
||||||
|
let liquidity = ObligationLiquidity::new(borrow_reserve);
|
||||||
|
self.borrows.push(liquidity);
|
||||||
|
Ok(self.borrows.last_mut().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
Ok(RepayResult {
|
fn _find_liquidity_index_in_borrows(&self, borrow_reserve: Pubkey) -> Option<usize> {
|
||||||
collateral_withdraw_amount,
|
self.borrows
|
||||||
obligation_token_amount,
|
.iter()
|
||||||
decimal_repay_amount,
|
.position(|liquidity| liquidity.borrow_reserve == borrow_reserve)
|
||||||
integer_repay_amount,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obligation repay result
|
/// Initialize an obligation
|
||||||
pub struct RepayResult {
|
pub struct InitObligationParams {
|
||||||
/// Amount of collateral to withdraw
|
/// Last update to collateral, liquidity, or their market values
|
||||||
pub collateral_withdraw_amount: u64,
|
pub current_slot: Slot,
|
||||||
/// Amount of obligation tokens to burn
|
/// Lending market address
|
||||||
pub obligation_token_amount: u64,
|
pub lending_market: Pubkey,
|
||||||
/// Amount that will be repaid as precise decimal
|
/// Owner authority which can borrow liquidity
|
||||||
pub decimal_repay_amount: Decimal,
|
pub owner: Pubkey,
|
||||||
/// Amount that will be repaid as u64
|
/// Deposited collateral for the obligation, unique by deposit reserve address
|
||||||
pub integer_repay_amount: u64,
|
pub deposits: Vec<ObligationCollateral>,
|
||||||
}
|
/// Borrowed liquidity for the obligation, unique by borrow reserve address
|
||||||
|
pub borrows: Vec<ObligationLiquidity>,
|
||||||
/// Create new obligation
|
|
||||||
pub struct NewObligationParams {
|
|
||||||
/// Collateral reserve address
|
|
||||||
pub collateral_reserve: Pubkey,
|
|
||||||
/// Borrow reserve address
|
|
||||||
pub borrow_reserve: Pubkey,
|
|
||||||
/// Obligation token mint address
|
|
||||||
pub token_mint: Pubkey,
|
|
||||||
/// Borrow rate used for calculating interest.
|
|
||||||
pub cumulative_borrow_rate_wads: Decimal,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sealed for Obligation {}
|
impl Sealed for Obligation {}
|
||||||
|
@ -189,55 +224,275 @@ impl IsInitialized for Obligation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const OBLIGATION_LEN: usize = 265;
|
/// Obligation collateral state
|
||||||
impl Pack for Obligation {
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
const LEN: usize = 265;
|
pub struct ObligationCollateral {
|
||||||
|
/// Reserve collateral is deposited to
|
||||||
|
pub deposit_reserve: Pubkey,
|
||||||
|
/// Amount of collateral deposited
|
||||||
|
pub deposited_amount: u64,
|
||||||
|
/// Collateral market value in quote currency
|
||||||
|
pub market_value: Decimal,
|
||||||
|
}
|
||||||
|
|
||||||
/// Unpacks a byte buffer into a [ObligationInfo](struct.ObligationInfo.html).
|
impl ObligationCollateral {
|
||||||
fn unpack_from_slice(input: &[u8]) -> Result<Self, ProgramError> {
|
/// Create new obligation collateral
|
||||||
let input = array_ref![input, 0, OBLIGATION_LEN];
|
pub fn new(deposit_reserve: Pubkey) -> Self {
|
||||||
|
Self {
|
||||||
|
deposit_reserve,
|
||||||
|
deposited_amount: 0,
|
||||||
|
market_value: Decimal::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Increase deposited collateral
|
||||||
|
pub fn deposit(&mut self, collateral_amount: u64) -> ProgramResult {
|
||||||
|
self.deposited_amount = self
|
||||||
|
.deposited_amount
|
||||||
|
.checked_add(collateral_amount)
|
||||||
|
.ok_or(LendingError::MathOverflow)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrease deposited collateral
|
||||||
|
pub fn withdraw(&mut self, collateral_amount: u64) -> ProgramResult {
|
||||||
|
self.deposited_amount = self
|
||||||
|
.deposited_amount
|
||||||
|
.checked_sub(collateral_amount)
|
||||||
|
.ok_or(LendingError::MathOverflow)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obligation liquidity state
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
pub struct ObligationLiquidity {
|
||||||
|
/// Reserve liquidity is borrowed from
|
||||||
|
pub borrow_reserve: Pubkey,
|
||||||
|
/// Borrow rate used for calculating interest
|
||||||
|
pub cumulative_borrow_rate_wads: Decimal,
|
||||||
|
/// Amount of liquidity borrowed plus interest
|
||||||
|
pub borrowed_amount_wads: Decimal,
|
||||||
|
/// Liquidity market value in quote currency
|
||||||
|
pub market_value: Decimal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObligationLiquidity {
|
||||||
|
/// Create new obligation liquidity
|
||||||
|
pub fn new(borrow_reserve: Pubkey) -> Self {
|
||||||
|
Self {
|
||||||
|
borrow_reserve,
|
||||||
|
cumulative_borrow_rate_wads: Decimal::one(),
|
||||||
|
borrowed_amount_wads: Decimal::zero(),
|
||||||
|
market_value: Decimal::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrease borrowed liquidity
|
||||||
|
pub fn repay(&mut self, settle_amount: Decimal) -> ProgramResult {
|
||||||
|
self.borrowed_amount_wads = self.borrowed_amount_wads.try_sub(settle_amount)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Increase borrowed liquidity
|
||||||
|
pub fn borrow(&mut self, borrow_amount: Decimal) -> ProgramResult {
|
||||||
|
self.borrowed_amount_wads = self.borrowed_amount_wads.try_add(borrow_amount)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accrue interest
|
||||||
|
pub fn accrue_interest(&mut self, cumulative_borrow_rate_wads: Decimal) -> ProgramResult {
|
||||||
|
match cumulative_borrow_rate_wads.cmp(&self.cumulative_borrow_rate_wads) {
|
||||||
|
Ordering::Less => {
|
||||||
|
msg!("Interest rate cannot be negative");
|
||||||
|
return Err(LendingError::NegativeInterestRate.into());
|
||||||
|
}
|
||||||
|
Ordering::Equal => {}
|
||||||
|
Ordering::Greater => {
|
||||||
|
let compounded_interest_rate: Rate = cumulative_borrow_rate_wads
|
||||||
|
.try_div(self.cumulative_borrow_rate_wads)?
|
||||||
|
.try_into()?;
|
||||||
|
|
||||||
|
self.borrowed_amount_wads = self
|
||||||
|
.borrowed_amount_wads
|
||||||
|
.try_mul(compounded_interest_rate)?;
|
||||||
|
self.cumulative_borrow_rate_wads = cumulative_borrow_rate_wads;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const OBLIGATION_COLLATERAL_LEN: usize = 56; // 32 + 8 + 16
|
||||||
|
const OBLIGATION_LIQUIDITY_LEN: usize = 80; // 32 + 16 + 16 + 16
|
||||||
|
const OBLIGATION_LEN: usize = 916; // 1 + 8 + 1 + 32 + 32 + 16 + 16 + 16 + 16 + 1 + 1 + (56 * 1) + (80 * 9)
|
||||||
|
// @TODO: break this up by obligation / collateral / liquidity https://git.io/JOCca
|
||||||
|
impl Pack for Obligation {
|
||||||
|
const LEN: usize = OBLIGATION_LEN;
|
||||||
|
|
||||||
|
fn pack_into_slice(&self, dst: &mut [u8]) {
|
||||||
|
let output = array_mut_ref![dst, 0, OBLIGATION_LEN];
|
||||||
#[allow(clippy::ptr_offset_with_cast)]
|
#[allow(clippy::ptr_offset_with_cast)]
|
||||||
let (
|
let (
|
||||||
version,
|
version,
|
||||||
deposited_collateral_tokens,
|
last_update_slot,
|
||||||
collateral_supply,
|
last_update_stale,
|
||||||
cumulative_borrow_rate,
|
lending_market,
|
||||||
borrowed_liquidity_wads,
|
owner,
|
||||||
borrow_reserve,
|
deposited_value,
|
||||||
token_mint,
|
borrowed_value,
|
||||||
_padding,
|
allowed_borrow_value,
|
||||||
) = array_refs![input, 1, 8, 32, 16, 16, 32, 32, 128];
|
unhealthy_borrow_value,
|
||||||
Ok(Self {
|
deposits_len,
|
||||||
version: u8::from_le_bytes(*version),
|
borrows_len,
|
||||||
deposited_collateral_tokens: u64::from_le_bytes(*deposited_collateral_tokens),
|
data_flat,
|
||||||
collateral_reserve: Pubkey::new_from_array(*collateral_supply),
|
) = mut_array_refs![
|
||||||
cumulative_borrow_rate_wads: unpack_decimal(cumulative_borrow_rate),
|
output,
|
||||||
borrowed_liquidity_wads: unpack_decimal(borrowed_liquidity_wads),
|
1,
|
||||||
borrow_reserve: Pubkey::new_from_array(*borrow_reserve),
|
8,
|
||||||
token_mint: Pubkey::new_from_array(*token_mint),
|
1,
|
||||||
})
|
PUBKEY_BYTES,
|
||||||
|
PUBKEY_BYTES,
|
||||||
|
16,
|
||||||
|
16,
|
||||||
|
16,
|
||||||
|
16,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
OBLIGATION_COLLATERAL_LEN + (OBLIGATION_LIQUIDITY_LEN * (MAX_OBLIGATION_RESERVES - 1))
|
||||||
|
];
|
||||||
|
|
||||||
|
// obligation
|
||||||
|
*version = self.version.to_le_bytes();
|
||||||
|
*last_update_slot = self.last_update.slot.to_le_bytes();
|
||||||
|
pack_bool(self.last_update.stale, last_update_stale);
|
||||||
|
lending_market.copy_from_slice(self.lending_market.as_ref());
|
||||||
|
owner.copy_from_slice(self.owner.as_ref());
|
||||||
|
pack_decimal(self.deposited_value, deposited_value);
|
||||||
|
pack_decimal(self.borrowed_value, borrowed_value);
|
||||||
|
pack_decimal(self.allowed_borrow_value, allowed_borrow_value);
|
||||||
|
pack_decimal(self.unhealthy_borrow_value, unhealthy_borrow_value);
|
||||||
|
*deposits_len = u8::try_from(self.deposits.len()).unwrap().to_le_bytes();
|
||||||
|
*borrows_len = u8::try_from(self.borrows.len()).unwrap().to_le_bytes();
|
||||||
|
|
||||||
|
let mut offset = 0;
|
||||||
|
|
||||||
|
// deposits
|
||||||
|
for collateral in &self.deposits {
|
||||||
|
let deposits_flat = array_mut_ref![data_flat, offset, OBLIGATION_COLLATERAL_LEN];
|
||||||
|
#[allow(clippy::ptr_offset_with_cast)]
|
||||||
|
let (deposit_reserve, deposited_amount, market_value) =
|
||||||
|
mut_array_refs![deposits_flat, PUBKEY_BYTES, 8, 16];
|
||||||
|
deposit_reserve.copy_from_slice(collateral.deposit_reserve.as_ref());
|
||||||
|
*deposited_amount = collateral.deposited_amount.to_le_bytes();
|
||||||
|
pack_decimal(collateral.market_value, market_value);
|
||||||
|
offset += OBLIGATION_COLLATERAL_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// borrows
|
||||||
|
for liquidity in &self.borrows {
|
||||||
|
let borrows_flat = array_mut_ref![data_flat, offset, OBLIGATION_LIQUIDITY_LEN];
|
||||||
|
#[allow(clippy::ptr_offset_with_cast)]
|
||||||
|
let (borrow_reserve, cumulative_borrow_rate_wads, borrowed_amount_wads, market_value) =
|
||||||
|
mut_array_refs![borrows_flat, PUBKEY_BYTES, 16, 16, 16];
|
||||||
|
borrow_reserve.copy_from_slice(liquidity.borrow_reserve.as_ref());
|
||||||
|
pack_decimal(
|
||||||
|
liquidity.cumulative_borrow_rate_wads,
|
||||||
|
cumulative_borrow_rate_wads,
|
||||||
|
);
|
||||||
|
pack_decimal(liquidity.borrowed_amount_wads, borrowed_amount_wads);
|
||||||
|
pack_decimal(liquidity.market_value, market_value);
|
||||||
|
offset += OBLIGATION_LIQUIDITY_LEN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack_into_slice(&self, output: &mut [u8]) {
|
/// Unpacks a byte buffer into an [ObligationInfo](struct.ObligationInfo.html).
|
||||||
let output = array_mut_ref![output, 0, OBLIGATION_LEN];
|
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
|
||||||
|
let input = array_ref![src, 0, OBLIGATION_LEN];
|
||||||
|
#[allow(clippy::ptr_offset_with_cast)]
|
||||||
let (
|
let (
|
||||||
version,
|
version,
|
||||||
deposited_collateral_tokens,
|
last_update_slot,
|
||||||
collateral_supply,
|
last_update_stale,
|
||||||
cumulative_borrow_rate,
|
lending_market,
|
||||||
borrowed_liquidity_wads,
|
owner,
|
||||||
borrow_reserve,
|
deposited_value,
|
||||||
token_mint,
|
borrowed_value,
|
||||||
_padding,
|
allowed_borrow_value,
|
||||||
) = mut_array_refs![output, 1, 8, 32, 16, 16, 32, 32, 128];
|
unhealthy_borrow_value,
|
||||||
|
deposits_len,
|
||||||
|
borrows_len,
|
||||||
|
data_flat,
|
||||||
|
) = array_refs![
|
||||||
|
input,
|
||||||
|
1,
|
||||||
|
8,
|
||||||
|
1,
|
||||||
|
PUBKEY_BYTES,
|
||||||
|
PUBKEY_BYTES,
|
||||||
|
16,
|
||||||
|
16,
|
||||||
|
16,
|
||||||
|
16,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
OBLIGATION_COLLATERAL_LEN + (OBLIGATION_LIQUIDITY_LEN * (MAX_OBLIGATION_RESERVES - 1))
|
||||||
|
];
|
||||||
|
|
||||||
*version = self.version.to_le_bytes();
|
let version = u8::from_le_bytes(*version);
|
||||||
*deposited_collateral_tokens = self.deposited_collateral_tokens.to_le_bytes();
|
if version > PROGRAM_VERSION {
|
||||||
collateral_supply.copy_from_slice(self.collateral_reserve.as_ref());
|
msg!("Obligation version does not match lending program version");
|
||||||
pack_decimal(self.cumulative_borrow_rate_wads, cumulative_borrow_rate);
|
return Err(ProgramError::InvalidAccountData);
|
||||||
pack_decimal(self.borrowed_liquidity_wads, borrowed_liquidity_wads);
|
}
|
||||||
borrow_reserve.copy_from_slice(self.borrow_reserve.as_ref());
|
|
||||||
token_mint.copy_from_slice(self.token_mint.as_ref());
|
let deposits_len = u8::from_le_bytes(*deposits_len);
|
||||||
|
let borrows_len = u8::from_le_bytes(*borrows_len);
|
||||||
|
let mut deposits = Vec::with_capacity(deposits_len as usize + 1);
|
||||||
|
let mut borrows = Vec::with_capacity(borrows_len as usize + 1);
|
||||||
|
|
||||||
|
let mut offset = 0;
|
||||||
|
for _ in 0..deposits_len {
|
||||||
|
let deposits_flat = array_ref![data_flat, offset, OBLIGATION_COLLATERAL_LEN];
|
||||||
|
#[allow(clippy::ptr_offset_with_cast)]
|
||||||
|
let (deposit_reserve, deposited_amount, market_value) =
|
||||||
|
array_refs![deposits_flat, PUBKEY_BYTES, 8, 16];
|
||||||
|
deposits.push(ObligationCollateral {
|
||||||
|
deposit_reserve: Pubkey::new(deposit_reserve),
|
||||||
|
deposited_amount: u64::from_le_bytes(*deposited_amount),
|
||||||
|
market_value: unpack_decimal(market_value),
|
||||||
|
});
|
||||||
|
offset += OBLIGATION_COLLATERAL_LEN;
|
||||||
|
}
|
||||||
|
for _ in 0..borrows_len {
|
||||||
|
let borrows_flat = array_ref![data_flat, offset, OBLIGATION_LIQUIDITY_LEN];
|
||||||
|
#[allow(clippy::ptr_offset_with_cast)]
|
||||||
|
let (borrow_reserve, cumulative_borrow_rate_wads, borrowed_amount_wads, market_value) =
|
||||||
|
array_refs![borrows_flat, PUBKEY_BYTES, 16, 16, 16];
|
||||||
|
borrows.push(ObligationLiquidity {
|
||||||
|
borrow_reserve: Pubkey::new(borrow_reserve),
|
||||||
|
cumulative_borrow_rate_wads: unpack_decimal(cumulative_borrow_rate_wads),
|
||||||
|
borrowed_amount_wads: unpack_decimal(borrowed_amount_wads),
|
||||||
|
market_value: unpack_decimal(market_value),
|
||||||
|
});
|
||||||
|
offset += OBLIGATION_LIQUIDITY_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
version,
|
||||||
|
last_update: LastUpdate {
|
||||||
|
slot: u64::from_le_bytes(*last_update_slot),
|
||||||
|
stale: unpack_bool(last_update_stale)?,
|
||||||
|
},
|
||||||
|
lending_market: Pubkey::new_from_array(*lending_market),
|
||||||
|
owner: Pubkey::new_from_array(*owner),
|
||||||
|
deposits,
|
||||||
|
borrows,
|
||||||
|
deposited_value: unpack_decimal(deposited_value),
|
||||||
|
borrowed_value: unpack_decimal(borrowed_value),
|
||||||
|
allowed_borrow_value: unpack_decimal(allowed_borrow_value),
|
||||||
|
unhealthy_borrow_value: unpack_decimal(unhealthy_borrow_value),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,28 +507,28 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn obligation_accrue_interest_failure() {
|
fn obligation_accrue_interest_failure() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Obligation {
|
ObligationLiquidity {
|
||||||
cumulative_borrow_rate_wads: Decimal::zero(),
|
cumulative_borrow_rate_wads: Decimal::zero(),
|
||||||
..Obligation::default()
|
..ObligationLiquidity::default()
|
||||||
}
|
}
|
||||||
.accrue_interest(Decimal::one()),
|
.accrue_interest(Decimal::one()),
|
||||||
Err(LendingError::MathOverflow.into())
|
Err(LendingError::MathOverflow.into())
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Obligation {
|
ObligationLiquidity {
|
||||||
cumulative_borrow_rate_wads: Decimal::from(2u64),
|
cumulative_borrow_rate_wads: Decimal::from(2u64),
|
||||||
..Obligation::default()
|
..ObligationLiquidity::default()
|
||||||
}
|
}
|
||||||
.accrue_interest(Decimal::one()),
|
.accrue_interest(Decimal::one()),
|
||||||
Err(LendingError::NegativeInterestRate.into())
|
Err(LendingError::NegativeInterestRate.into())
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Obligation {
|
ObligationLiquidity {
|
||||||
cumulative_borrow_rate_wads: Decimal::one(),
|
cumulative_borrow_rate_wads: Decimal::one(),
|
||||||
borrowed_liquidity_wads: Decimal::from(u64::MAX),
|
borrowed_amount_wads: Decimal::from(u64::MAX),
|
||||||
..Obligation::default()
|
..ObligationLiquidity::default()
|
||||||
}
|
}
|
||||||
.accrue_interest(Decimal::from(10 * MAX_COMPOUNDED_INTEREST)),
|
.accrue_interest(Decimal::from(10 * MAX_COMPOUNDED_INTEREST)),
|
||||||
Err(LendingError::MathOverflow.into())
|
Err(LendingError::MathOverflow.into())
|
||||||
|
@ -284,7 +539,7 @@ mod test {
|
||||||
prop_compose! {
|
prop_compose! {
|
||||||
fn cumulative_rates()(rate in 1..=u128::MAX)(
|
fn cumulative_rates()(rate in 1..=u128::MAX)(
|
||||||
current_rate in Just(rate),
|
current_rate in Just(rate),
|
||||||
max_new_rate in rate..=rate.saturating_mul(MAX_COMPOUNDED_INTEREST as u128)
|
max_new_rate in rate..=rate.saturating_mul(MAX_COMPOUNDED_INTEREST as u128),
|
||||||
) -> (u128, u128) {
|
) -> (u128, u128) {
|
||||||
(current_rate, max_new_rate)
|
(current_rate, max_new_rate)
|
||||||
}
|
}
|
||||||
|
@ -294,89 +549,81 @@ mod test {
|
||||||
|
|
||||||
// Creates liquidity amounts (repay, borrow) where repay < borrow
|
// Creates liquidity amounts (repay, borrow) where repay < borrow
|
||||||
prop_compose! {
|
prop_compose! {
|
||||||
fn repay_partial_amounts()(repay in 1..=u64::MAX)(
|
fn repay_partial_amounts()(amount in 1..=u64::MAX)(
|
||||||
liquidity_amount in Just(repay),
|
repay_amount in Just(WAD as u128 * amount as u128),
|
||||||
borrowed_liquidity in (WAD as u128 * repay as u128 + 1)..=MAX_BORROWED
|
borrowed_amount in (WAD as u128 * amount as u128 + 1)..=MAX_BORROWED,
|
||||||
) -> (u64, u128) {
|
) -> (u128, u128) {
|
||||||
(liquidity_amount, borrowed_liquidity)
|
(repay_amount, borrowed_amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates liquidity amounts (repay, borrow) where repay >= borrow
|
// Creates liquidity amounts (repay, borrow) where repay >= borrow
|
||||||
prop_compose! {
|
prop_compose! {
|
||||||
fn repay_full_amounts()(repay in 1..=u64::MAX)(
|
fn repay_full_amounts()(amount in 1..=u64::MAX)(
|
||||||
liquidity_amount in Just(repay),
|
repay_amount in Just(WAD as u128 * amount as u128),
|
||||||
borrowed_liquidity in 0..=(WAD as u128 * repay as u128)
|
) -> (u128, u128) {
|
||||||
) -> (u64, u128) {
|
(repay_amount, repay_amount)
|
||||||
(liquidity_amount, borrowed_liquidity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates collateral amounts (collateral, obligation tokens) where c <= ot
|
|
||||||
prop_compose! {
|
|
||||||
fn collateral_amounts()(collateral in 1..=u64::MAX)(
|
|
||||||
deposited_collateral_tokens in Just(collateral),
|
|
||||||
obligation_tokens in collateral..=u64::MAX
|
|
||||||
) -> (u64, u64) {
|
|
||||||
(deposited_collateral_tokens, obligation_tokens)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proptest! {
|
proptest! {
|
||||||
#[test]
|
#[test]
|
||||||
fn repay_partial(
|
fn repay_partial(
|
||||||
(liquidity_amount, borrowed_liquidity) in repay_partial_amounts(),
|
(repay_amount, borrowed_amount) in repay_partial_amounts(),
|
||||||
(deposited_collateral_tokens, obligation_tokens) in collateral_amounts(),
|
|
||||||
) {
|
) {
|
||||||
let borrowed_liquidity_wads = Decimal::from_scaled_val(borrowed_liquidity);
|
let borrowed_amount_wads = Decimal::from_scaled_val(borrowed_amount);
|
||||||
let mut state = Obligation { deposited_collateral_tokens, borrowed_liquidity_wads, ..Obligation::default() };
|
let repay_amount_wads = Decimal::from_scaled_val(repay_amount);
|
||||||
|
let mut obligation = Obligation {
|
||||||
|
borrows: vec![ObligationLiquidity {
|
||||||
|
borrowed_amount_wads,
|
||||||
|
..ObligationLiquidity::default()
|
||||||
|
}],
|
||||||
|
..Obligation::default()
|
||||||
|
};
|
||||||
|
|
||||||
let repay_result = state.repay(liquidity_amount, obligation_tokens)?;
|
obligation.repay(repay_amount_wads, 0)?;
|
||||||
assert!(repay_result.decimal_repay_amount <= Decimal::from(repay_result.integer_repay_amount));
|
assert!(obligation.borrows[0].borrowed_amount_wads < borrowed_amount_wads);
|
||||||
assert!(repay_result.collateral_withdraw_amount < deposited_collateral_tokens);
|
assert!(obligation.borrows[0].borrowed_amount_wads > Decimal::zero());
|
||||||
assert!(repay_result.obligation_token_amount < obligation_tokens);
|
|
||||||
assert!(state.borrowed_liquidity_wads < borrowed_liquidity_wads);
|
|
||||||
assert!(state.borrowed_liquidity_wads > Decimal::zero());
|
|
||||||
assert!(state.deposited_collateral_tokens > 0);
|
|
||||||
|
|
||||||
let obligation_token_rate = Decimal::from(repay_result.obligation_token_amount).try_div(Decimal::from(obligation_tokens))?;
|
|
||||||
let collateral_withdraw_rate = Decimal::from(repay_result.collateral_withdraw_amount).try_div(Decimal::from(deposited_collateral_tokens))?;
|
|
||||||
assert!(obligation_token_rate <= collateral_withdraw_rate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn repay_full(
|
fn repay_full(
|
||||||
(liquidity_amount, borrowed_liquidity) in repay_full_amounts(),
|
(repay_amount, borrowed_amount) in repay_full_amounts(),
|
||||||
(deposited_collateral_tokens, obligation_tokens) in collateral_amounts(),
|
|
||||||
) {
|
) {
|
||||||
let borrowed_liquidity_wads = Decimal::from_scaled_val(borrowed_liquidity);
|
let borrowed_amount_wads = Decimal::from_scaled_val(borrowed_amount);
|
||||||
let mut state = Obligation { deposited_collateral_tokens, borrowed_liquidity_wads, ..Obligation::default() } ;
|
let repay_amount_wads = Decimal::from_scaled_val(repay_amount);
|
||||||
|
let mut obligation = Obligation {
|
||||||
|
borrows: vec![ObligationLiquidity {
|
||||||
|
borrowed_amount_wads,
|
||||||
|
..ObligationLiquidity::default()
|
||||||
|
}],
|
||||||
|
..Obligation::default()
|
||||||
|
};
|
||||||
|
|
||||||
let repay_result = state.repay(liquidity_amount, obligation_tokens)?;
|
obligation.repay(repay_amount_wads, 0)?;
|
||||||
assert!(repay_result.decimal_repay_amount <= Decimal::from(repay_result.integer_repay_amount));
|
assert_eq!(obligation.borrows.len(), 0);
|
||||||
assert_eq!(repay_result.collateral_withdraw_amount, deposited_collateral_tokens);
|
|
||||||
assert_eq!(repay_result.obligation_token_amount, obligation_tokens);
|
|
||||||
assert_eq!(repay_result.decimal_repay_amount, borrowed_liquidity_wads);
|
|
||||||
assert_eq!(state.borrowed_liquidity_wads, Decimal::zero());
|
|
||||||
assert_eq!(state.deposited_collateral_tokens, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn accrue_interest(
|
fn accrue_interest(
|
||||||
borrowed_liquidity in 0..=u64::MAX,
|
|
||||||
(current_borrow_rate, new_borrow_rate) in cumulative_rates(),
|
(current_borrow_rate, new_borrow_rate) in cumulative_rates(),
|
||||||
|
borrowed_amount in 0..=u64::MAX,
|
||||||
) {
|
) {
|
||||||
let borrowed_liquidity_wads = Decimal::from(borrowed_liquidity);
|
|
||||||
let cumulative_borrow_rate_wads = Decimal::one().try_add(Decimal::from_scaled_val(current_borrow_rate))?;
|
let cumulative_borrow_rate_wads = Decimal::one().try_add(Decimal::from_scaled_val(current_borrow_rate))?;
|
||||||
let mut state = Obligation { cumulative_borrow_rate_wads, borrowed_liquidity_wads, ..Obligation::default() };
|
let borrowed_amount_wads = Decimal::from(borrowed_amount);
|
||||||
|
let mut liquidity = ObligationLiquidity {
|
||||||
|
cumulative_borrow_rate_wads,
|
||||||
|
borrowed_amount_wads,
|
||||||
|
..ObligationLiquidity::default()
|
||||||
|
};
|
||||||
|
|
||||||
let next_cumulative_borrow_rate = Decimal::one().try_add(Decimal::from_scaled_val(new_borrow_rate))?;
|
let next_cumulative_borrow_rate = Decimal::one().try_add(Decimal::from_scaled_val(new_borrow_rate))?;
|
||||||
state.accrue_interest(next_cumulative_borrow_rate)?;
|
liquidity.accrue_interest(next_cumulative_borrow_rate)?;
|
||||||
|
|
||||||
if next_cumulative_borrow_rate > cumulative_borrow_rate_wads {
|
if next_cumulative_borrow_rate > cumulative_borrow_rate_wads {
|
||||||
assert!(state.borrowed_liquidity_wads > borrowed_liquidity_wads);
|
assert!(liquidity.borrowed_amount_wads > borrowed_amount_wads);
|
||||||
} else {
|
} else {
|
||||||
assert!(state.borrowed_liquidity_wads == borrowed_liquidity_wads);
|
assert!(liquidity.borrowed_amount_wads == borrowed_amount_wads);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue