Skip to content

Commit

Permalink
[contract] fund settlement with non-delegated lamports
Browse files Browse the repository at this point in the history
  • Loading branch information
ochaloup committed Sep 11, 2024
1 parent 04a2865 commit e43904b
Show file tree
Hide file tree
Showing 5 changed files with 455 additions and 38 deletions.
269 changes: 269 additions & 0 deletions packages/validator-bonds-sdk/__tests__/bankrun/fundSettlement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,275 @@ describe('Validator Bonds fund settlement', () => {
)
})

it.each([50, 0.5, 0.1])(
'fund settlement non-delegated split: %d',
async totalClaim => {
const maxTotalClaim = totalClaim * LAMPORTS_PER_SOL
const { settlementAccount } = await executeInitSettlement({
configAccount,
program,
provider,
voteAccount,
operatorAuthority,
currentEpoch: settlementEpoch,
maxTotalClaim,
})

const lamportsToFund = 4444 * LAMPORTS_PER_SOL
const stakeAccount =
await createBondsFundedStakeAccountActivated(lamportsToFund)
let stakeAccountData =
await provider.connection.getAccountInfo(stakeAccount)
expect(stakeAccountData?.lamports).toEqual(lamportsToFund)
// adding some more lamports to stake account that are not delegated
// when the amount of non-delegated lamports is bigger
const stakeAccountTransferredLamports =
maxTotalClaim + rentExemptStake + 1
const transferIx = SystemProgram.transfer({
fromPubkey: provider.wallet.publicKey,
toPubkey: stakeAccount,
lamports: stakeAccountTransferredLamports,
})
await provider.sendIx([], transferIx)
await getAndCheckStakeAccount(
provider,
stakeAccount,
StakeStates.Delegated
)
stakeAccountData = await provider.connection.getAccountInfo(stakeAccount)
expect(stakeAccountData?.lamports).toEqual(
lamportsToFund + stakeAccountTransferredLamports
)

let settlementData = await getSettlement(program, settlementAccount)
expect(settlementData.lamportsFunded).toEqual(0)

const { instruction, splitStakeAccount } =
await fundSettlementInstruction({
program,
settlementAccount,
stakeAccount: stakeAccount,
})
await provider.sendIx(
[signer(splitStakeAccount), operatorAuthority],
instruction
)

settlementData = await getSettlement(program, settlementAccount)
stakeAccountData = await provider.connection.getAccountInfo(stakeAccount)
const splitStakeAccountData = await provider.connection.getAccountInfo(
pubkey(splitStakeAccount)
)

expect(stakeAccountData?.lamports).toEqual(
maxTotalClaim +
2 * rentExemptStake + // rent for stake account + for returning rent exempt for split stake account
config.minimumStakeLamports.toNumber()
)
expect(splitStakeAccountData?.lamports).toEqual(
lamportsToFund +
stakeAccountTransferredLamports -
maxTotalClaim -
config.minimumStakeLamports.toNumber() -
rentExemptStake
)
expect(settlementData.lamportsFunded).toEqual(maxTotalClaim)
}
)

it.each([50, 0.5, 0.1])(
'fund settlement minimum delegated amount: %d',
async totalClaim => {
const maxTotalClaim = totalClaim * LAMPORTS_PER_SOL
const { settlementAccount } = await executeInitSettlement({
configAccount,
program,
provider,
voteAccount,
operatorAuthority,
currentEpoch: settlementEpoch,
maxTotalClaim,
})

const lamportsToFund =
config.minimumStakeLamports.toNumber() + rentExemptStake
const stakeAccount =
await createBondsFundedStakeAccountActivated(lamportsToFund)
let stakeAccountData =
await provider.connection.getAccountInfo(stakeAccount)
expect(stakeAccountData?.lamports).toEqual(lamportsToFund)
const stakeAccountTransferredLamports = 500 * LAMPORTS_PER_SOL
const transferIx = SystemProgram.transfer({
fromPubkey: provider.wallet.publicKey,
toPubkey: stakeAccount,
lamports: stakeAccountTransferredLamports,
})
await provider.sendIx([], transferIx)
await getAndCheckStakeAccount(
provider,
stakeAccount,
StakeStates.Delegated
)
stakeAccountData = await provider.connection.getAccountInfo(stakeAccount)
expect(stakeAccountData?.lamports).toEqual(
lamportsToFund + stakeAccountTransferredLamports
)

let settlementData = await getSettlement(program, settlementAccount)
expect(settlementData.lamportsFunded).toEqual(0)

const { instruction, splitStakeAccount } =
await fundSettlementInstruction({
program,
settlementAccount,
stakeAccount: stakeAccount,
})
await provider.sendIx(
[signer(splitStakeAccount), operatorAuthority],
instruction
)

settlementData = await getSettlement(program, settlementAccount)
stakeAccountData = await provider.connection.getAccountInfo(stakeAccount)
const splitStakeAccountData = await provider.connection.getAccountInfo(
pubkey(splitStakeAccount)
)

expect(stakeAccountData?.lamports).toEqual(
maxTotalClaim +
2 * rentExemptStake + // rent for stake account + for returning rent exempt for split stake account
config.minimumStakeLamports.toNumber()
)
expect(splitStakeAccountData?.lamports).toEqual(
lamportsToFund +
stakeAccountTransferredLamports -
maxTotalClaim -
config.minimumStakeLamports.toNumber() -
rentExemptStake
)
expect(settlementData.lamportsFunded).toEqual(maxTotalClaim)
await getAndCheckStakeAccount(
provider,
pubkey(splitStakeAccount),
StakeStates.Delegated
)
}
)

it('double fund settlement minimum delegated amount', async () => {
const maxTotalClaim = 50 * LAMPORTS_PER_SOL
const { settlementAccount } = await executeInitSettlement({
configAccount,
program,
provider,
voteAccount,
operatorAuthority,
currentEpoch: settlementEpoch,
maxTotalClaim,
})
let settlementData = await getSettlement(program, settlementAccount)
expect(settlementData.lamportsFunded).toEqual(0)

// 1st stake account
const lamportsToFund =
config.minimumStakeLamports.toNumber() + rentExemptStake
const stakeAccount1 =
await createBondsFundedStakeAccountActivated(lamportsToFund)
let stakeAccountData1 =
await provider.connection.getAccountInfo(stakeAccount1)
expect(stakeAccountData1?.lamports).toEqual(lamportsToFund)
const stakeAccountTransferredLamports1 = 10 * LAMPORTS_PER_SOL
const transferIx1 = SystemProgram.transfer({
fromPubkey: provider.wallet.publicKey,
toPubkey: stakeAccount1,
lamports: stakeAccountTransferredLamports1,
})
await provider.sendIx([], transferIx1)
await getAndCheckStakeAccount(
provider,
stakeAccount1,
StakeStates.Delegated
)
stakeAccountData1 = await provider.connection.getAccountInfo(stakeAccount1)
expect(stakeAccountData1?.lamports).toEqual(
lamportsToFund + stakeAccountTransferredLamports1
)

// 2nd stake account
const stakeAccount2 =
await createBondsFundedStakeAccountActivated(lamportsToFund)
let stakeAccountData2 =
await provider.connection.getAccountInfo(stakeAccount2)
expect(stakeAccountData2?.lamports).toEqual(lamportsToFund)
const stakeAccountTransferredLamports2 = 100 * LAMPORTS_PER_SOL
const transferIx2 = SystemProgram.transfer({
fromPubkey: provider.wallet.publicKey,
toPubkey: stakeAccount2,
lamports: stakeAccountTransferredLamports2,
})
await provider.sendIx([], transferIx2)
await getAndCheckStakeAccount(
provider,
stakeAccount2,
StakeStates.Delegated
)
stakeAccountData2 = await provider.connection.getAccountInfo(stakeAccount2)
expect(stakeAccountData2?.lamports).toEqual(
lamportsToFund + stakeAccountTransferredLamports2
)

const { instruction: ixFund1, splitStakeAccount: split1 } =
await fundSettlementInstruction({
program,
settlementAccount,
stakeAccount: stakeAccount1,
})
await provider.sendIx([signer(split1), operatorAuthority], ixFund1)
assertNotExist(provider, pubkey(split1))
settlementData = await getSettlement(program, settlementAccount)
expect(settlementData.lamportsFunded).toEqual(
stakeAccountTransferredLamports1
)

const { instruction: ixFund2, splitStakeAccount: split2 } =
await fundSettlementInstruction({
program,
settlementAccount,
stakeAccount: stakeAccount2,
})
await provider.sendIx([signer(split2), operatorAuthority], ixFund2)

settlementData = await getSettlement(program, settlementAccount)
expect(settlementData.lamportsFunded).toEqual(maxTotalClaim)

stakeAccountData1 = await provider.connection.getAccountInfo(stakeAccount1)
stakeAccountData2 = await provider.connection.getAccountInfo(stakeAccount2)
const splitStakeAccountData2 = await provider.connection.getAccountInfo(
pubkey(split2)
)

expect(stakeAccountData1?.lamports).toEqual(
stakeAccountTransferredLamports1 +
rentExemptStake + // no split, just rent for stake account
config.minimumStakeLamports.toNumber()
)
expect(stakeAccountData2?.lamports).toEqual(
maxTotalClaim -
stakeAccountTransferredLamports1 +
2 * rentExemptStake + // rent for stake account + for returning rent exempt for split stake account
config.minimumStakeLamports.toNumber()
)
expect(splitStakeAccountData2?.lamports).toEqual(
stakeAccountTransferredLamports2 -
(maxTotalClaim - stakeAccountTransferredLamports1)
)
await getAndCheckStakeAccount(
provider,
pubkey(split2),
StakeStates.Delegated
)
})

async function createBondsFundedStakeAccountActivated(
lamports: number
): Promise<PublicKey> {
Expand Down
32 changes: 26 additions & 6 deletions packages/validator-bonds-sdk/generated/validator_bonds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1484,15 +1484,20 @@ export type ValidatorBonds = {
{
"kind": "account",
"type": "publicKey",
"account": "Bond",
"path": "bond.vote_account"
"path": "vote_account"
}
]
},
"relations": [
"config"
"config",
"vote_account"
]
},
{
"name": "voteAccount",
"isMut": false,
"isSigner": false
},
{
"name": "settlement",
"isMut": true,
Expand Down Expand Up @@ -1643,6 +1648,11 @@ export type ValidatorBonds = {
"isMut": false,
"isSigner": false
},
{
"name": "stakeConfig",
"isMut": false,
"isSigner": false
},
{
"name": "eventAuthority",
"isMut": false,
Expand Down Expand Up @@ -6576,15 +6586,20 @@ export const IDL: ValidatorBonds = {
{
"kind": "account",
"type": "publicKey",
"account": "Bond",
"path": "bond.vote_account"
"path": "vote_account"
}
]
},
"relations": [
"config"
"config",
"vote_account"
]
},
{
"name": "voteAccount",
"isMut": false,
"isSigner": false
},
{
"name": "settlement",
"isMut": true,
Expand Down Expand Up @@ -6735,6 +6750,11 @@ export const IDL: ValidatorBonds = {
"isMut": false,
"isSigner": false
},
{
"name": "stakeConfig",
"isMut": false,
"isSigner": false
},
{
"name": "eventAuthority",
"isMut": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Keypair,
Signer,
SYSVAR_RENT_PUBKEY,
STAKE_CONFIG_ID,
} from '@solana/web3.js'
import { ValidatorBondsProgram, bondAddress } from '../sdk'
import { getBond, getConfig, getSettlement } from '../api'
Expand Down Expand Up @@ -56,9 +57,10 @@ export async function fundSettlementInstruction({
bondAccount = settlementData.bond
}

if (configAccount === undefined) {
if (configAccount === undefined || voteAccount === undefined) {
const bondData = await getBond(program, bondAccount)
configAccount = bondData.config
voteAccount = bondData.voteAccount
}

if (operatorAuthority === undefined) {
Expand Down Expand Up @@ -86,13 +88,15 @@ export async function fundSettlementInstruction({
bond: bondAccount,
settlement: settlementAccount,
operatorAuthority: operatorAuthorityPubkey,
voteAccount: voteAccount,
stakeAccount,
splitStakeAccount: splitStakeAccountPubkey,
splitStakeRentPayer: splitStakeRentPayerPubkey,
stakeHistory: SYSVAR_STAKE_HISTORY_PUBKEY,
rent: SYSVAR_RENT_PUBKEY,
clock: SYSVAR_CLOCK_PUBKEY,
stakeProgram: StakeProgram.programId,
stakeConfig: STAKE_CONFIG_ID,
})
.instruction()
return {
Expand Down
Loading

0 comments on commit e43904b

Please sign in to comment.