Skip to content

Commit

Permalink
SPL Stake Pool extract yield to stake account. Test complete
Browse files Browse the repository at this point in the history
  • Loading branch information
dankelleher committed Jan 23, 2024
1 parent bcd65c6 commit caa212d
Show file tree
Hide file tree
Showing 15 changed files with 186 additions and 55 deletions.
2 changes: 1 addition & 1 deletion packages/sdks/common-marinade/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"dist"
],
"scripts": {
"build": "rm -r dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"build": "rm -rf dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"lint": "tsc --noEmit && eslint -c ../../../.eslintrc.yaml --ext .ts,.tsx src"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/sdks/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"dist"
],
"scripts": {
"build": "rm -r dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"build": "rm -rf dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"lint": "tsc --noEmit && eslint -c ../../../.eslintrc.yaml --ext .ts,.tsx src"
},
"dependencies": {
Expand Down
40 changes: 38 additions & 2 deletions packages/sdks/common/src/types/spl_beam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,10 @@ export type SplBeam = {
{
"name": "newStakeAccount",
"isMut": true,
"isSigner": false
"isSigner": false,
"docs": [
"The uninitialized new stake account. Will be initialised by CPI to the SPL StakePool program."
]
},
{
"name": "vaultAuthority",
Expand Down Expand Up @@ -711,6 +714,11 @@ export type SplBeam = {
"isMut": false,
"isSigner": false
},
{
"name": "sysvarStakeHistory",
"isMut": false,
"isSigner": false
},
{
"name": "sunriseProgram",
"isMut": false,
Expand Down Expand Up @@ -815,6 +823,16 @@ export type SplBeam = {
"code": 6002,
"name": "Unimplemented",
"msg": "This feature is unimplemented for this beam"
},
{
"code": 6003,
"name": "YieldStakeAccountNotCooledDown",
"msg": "The yield stake account cannot yet be claimed"
},
{
"code": 6004,
"name": "InsufficientYieldToExtract",
"msg": "The yield being extracted is insufficient to cover the rent of the stake account"
}
]
};
Expand Down Expand Up @@ -1490,7 +1508,10 @@ export const IDL: SplBeam = {
{
"name": "newStakeAccount",
"isMut": true,
"isSigner": false
"isSigner": false,
"docs": [
"The uninitialized new stake account. Will be initialised by CPI to the SPL StakePool program."
]
},
{
"name": "vaultAuthority",
Expand Down Expand Up @@ -1532,6 +1553,11 @@ export const IDL: SplBeam = {
"isMut": false,
"isSigner": false
},
{
"name": "sysvarStakeHistory",
"isMut": false,
"isSigner": false
},
{
"name": "sunriseProgram",
"isMut": false,
Expand Down Expand Up @@ -1636,6 +1662,16 @@ export const IDL: SplBeam = {
"code": 6002,
"name": "Unimplemented",
"msg": "This feature is unimplemented for this beam"
},
{
"code": 6003,
"name": "YieldStakeAccountNotCooledDown",
"msg": "The yield stake account cannot yet be claimed"
},
{
"code": 6004,
"name": "InsufficientYieldToExtract",
"msg": "The yield being extracted is insufficient to cover the rent of the stake account"
}
]
};
2 changes: 1 addition & 1 deletion packages/sdks/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"dist"
],
"scripts": {
"build": "rm -r dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"build": "rm -rf dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"lint": "tsc --noEmit && eslint -c ../../../.eslintrc.yaml --ext .ts,.tsx src"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/sdks/marinade-lp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"dist"
],
"scripts": {
"build": "rm -r dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"build": "rm -rf dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"lint": "tsc --noEmit && eslint -c ../../../.eslintrc.yaml --ext .ts,.tsx src"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/sdks/marinade-sp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"dist"
],
"scripts": {
"build": "rm -r dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"build": "rm -rf dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"lint": "tsc --noEmit && eslint -c ../../../.eslintrc.yaml --ext .ts,.tsx src"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/sdks/spl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"dist"
],
"scripts": {
"build": "rm -r dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"build": "rm -rf dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"lint": "tsc --noEmit && eslint -c ../../../.eslintrc.yaml --ext .ts,.tsx src"
},
"dependencies": {
Expand Down
1 change: 1 addition & 0 deletions packages/sdks/spl/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ export class SplClient extends BeamInterface<SplBeam.SplBeam, StateAccount> {
managerFeeAccount: this.spl.stakePoolState.managerFeeAccount,
sysvarClock: SYSVAR_CLOCK_PUBKEY,
nativeStakeProgram: StakeProgram.programId,
sysvarStakeHistory: SYSVAR_STAKE_HISTORY_PUBKEY,
sunriseProgram: this.sunrise.program.programId,
splStakePoolProgram: SPL_STAKE_POOL_PROGRAM_ID,
systemProgram: SystemProgram.programId,
Expand Down
2 changes: 1 addition & 1 deletion packages/sdks/sunrise-stake-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"dist"
],
"scripts": {
"build": "rm -r dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"build": "rm -rf dist; tsc --outDir dist/esm; tsc --module commonjs --moduleResolution node --outDir dist/cjs && yarn typedoc",
"lint": "tsc --noEmit && eslint -c ../../../.eslintrc.yaml --ext .ts,.tsx src"
},
"dependencies": {
Expand Down
39 changes: 8 additions & 31 deletions packages/tests/src/functional/beams/spl-stake-pool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import { SplClient } from "@sunrisestake/beams-spl";
import { Keypair, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
import BN from "bn.js";
import {
expectStakeAccountBalance,
expectSolBalance,
expectTokenBalance,
fund,
registerSunriseState,
sendAndConfirmTransaction,
tokenAccountBalance,
waitForNextEpoch,
} from "../../utils.js";
import { provider, staker, stakerIdentity } from "../setup.js";
import { expect } from "chai";
Expand Down Expand Up @@ -205,48 +204,26 @@ describe("SPL stake pool beam", () => {

it("can extract yield into a stake account", async () => {
// since we burned some sol - we now have yield to extract (the value of the LPs is higher than the value of the GSOL staked)
// The beam performs a delayed unstake to reduce fees, so the result is a stake account with the yield in it.

await sendAndConfirmTransaction(
// anyone can extract yield to the yield account, but let's use the staker provider (rather than the admin provider) for this test
// to show that it doesn't have to be an admin
stakerIdentity,
await beamClient.extractYield(),
);

// we burned `burnAmount` gsol, so we should have `burnAmount` - fee in the stake account
// we burned `burnAmount` gsol, so we should have `burnAmount` in the yield account
const expectedFee = burnAmount
.mul(beamClient.spl.stakePoolState.stakeWithdrawalFee.numerator)
.div(beamClient.spl.stakePoolState.stakeWithdrawalFee.denominator);
const expectedStakeAmount = burnAmount.sub(expectedFee);

// there is no yield yet, but we have created a stake account for the yield
await expectStakeAccountBalance(
beamClient.provider,
beamClient.yieldStakeAccount,
expectedStakeAmount,
1,
);
});

it.skip("can claim stake account into yield account after cooldown", async () => {
// wait an epoch for the stake account to cool down
await waitForNextEpoch(beamClient.provider);
const expectedExtractedYield = burnAmount.sub(expectedFee);

// TODO enable
// await sendAndConfirmTransaction(
// // anyone can extract yield to the yield account, but let's use the staker provider (rather than the admin provider) for this test
// // to show that it doesn't have to be an admin
// stakerIdentity,
// // TODO move this and extract yield into "management" object to make it clear that these
// // do not need to be executed by end-users
// await beamClient.claimExtractedYieldStakeAcccount(),
// );

await expectTokenBalance(
await expectSolBalance(
beamClient.provider,
beamClient.sunrise.state.yieldAccount,
burnAmount,
expectedExtractedYield,
// the calculation appears to be slightly inaccurate at present, but in our favour,
// so we can leave this as a low priority TODO to improve the accuracy
3000,
);
});
});
13 changes: 12 additions & 1 deletion packages/tests/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,15 @@ export const expectStakerSolBalance = async (
provider: AnchorProvider,
expectedAmount: number | BN,
tolerance = 0, // Allow for a tolerance as the balance depends on the fees which are unstable at the beginning of a test validator
) => expectSolBalance(provider, provider.publicKey, expectedAmount, tolerance);

export const expectSolBalance = async (
provider: AnchorProvider,
address = provider.publicKey,
expectedAmount: number | BN,
tolerance = 0, // Allow for a tolerance as the balance depends on the fees which are unstable at the beginning of a test validator
) => {
const actualAmount = await solBalance(provider);
const actualAmount = await solBalance(provider, address);
expectAmount(actualAmount, expectedAmount, tolerance);
};

Expand Down Expand Up @@ -140,10 +147,14 @@ export const waitForNextEpoch = async (provider: AnchorProvider) => {
const startSlot = startingEpoch.slotIndex;
let subscriptionId = 0;

log("Waiting for epoch", nextEpoch);

await new Promise((resolve) => {
subscriptionId = provider.connection.onSlotChange((slotInfo) => {
log("slot", slotInfo.slot, "startSlot", startSlot);
if (slotInfo.slot % SLOTS_IN_EPOCH === 1 && slotInfo.slot > startSlot) {
void provider.connection.getEpochInfo().then((currentEpoch) => {
log("currentEpoch", currentEpoch);
if (currentEpoch.epoch === nextEpoch) {
resolve(slotInfo.slot);
}
Expand Down
20 changes: 17 additions & 3 deletions programs/spl-beam/src/cpi_interface/spl.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::constants::STAKE_ACCOUNT_SIZE;
use crate::cpi_interface::stake_pool::StakePool;
use crate::seeds::*;
use crate::state::State;
use crate::{ExtractYield, WithdrawStake};
use crate::{ExtractYield, SplBeamError, WithdrawStake};
use anchor_lang::{
prelude::*,
solana_program::program::{invoke, invoke_signed},
Expand Down Expand Up @@ -222,9 +223,22 @@ pub fn extract_stake(accounts: &ExtractStakeAccount, lamports: u64) -> Result<()
let state_address = accounts.state.key();
let seeds = &[state_address.as_ref(), VAULT_AUTHORITY, bump][..];

let stake_account_rent = Rent::get()?.minimum_balance(STAKE_ACCOUNT_SIZE);
let mut total_extractable_lamports = lamports.saturating_sub(stake_account_rent);

if total_extractable_lamports > accounts.stake_to_split.lamports() {
total_extractable_lamports = accounts.stake_to_split.lamports();
msg!("Limiting the extraction to the amount of lamports in the reserve stake account of the pool: {}", total_extractable_lamports);
}
if total_extractable_lamports == 0 {
return Err(SplBeamError::InsufficientYieldToExtract.into());
}

let pool = &accounts.stake_pool;
let pool_tokens =
crate::utils::pool_tokens_from_lamports(&pool.clone().into_inner(), lamports)?;
let pool_tokens = crate::utils::pool_tokens_from_lamports(
&pool.clone().into_inner(),
total_extractable_lamports,
)?;

invoke_signed(
&spl_stake_pool::instruction::withdraw_stake(
Expand Down
Loading

0 comments on commit caa212d

Please sign in to comment.