Skip to content

Commit

Permalink
[contract] merge stake fixing correct signature for merging
Browse files Browse the repository at this point in the history
  • Loading branch information
ochaloup committed Jul 3, 2024
1 parent 1eba941 commit b23f354
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 11 deletions.
177 changes: 171 additions & 6 deletions packages/validator-bonds-sdk/__tests__/bankrun/mergeStake.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,27 @@ import {
settlementAddress,
settlementStakerAuthority,
bondsWithdrawerAuthority,
getRentExemptStake,
getConfig,
getSettlement,
fundSettlementInstruction,
getStakeAccount,
} from '../../src'
import {
BankrunExtendedProvider,
assertNotExist,
currentEpoch,
warpToEpoch,
warpToNextEpoch,
} from '@marinade.finance/bankrun-utils'
import {
executeInitBondInstruction,
executeInitConfigInstruction,
executeInitSettlement,
executeWithdraw,
} from '../utils/testTransactions'
import {
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
SYSVAR_STAKE_HISTORY_PUBKEY,
Expand All @@ -29,15 +39,17 @@ import {
createInitializedStakeAccount,
getAndCheckStakeAccount,
StakeStates,
createBondsFundedStakeAccount,
} from '../utils/staking'
import { pubkey } from '@marinade.finance/web3js-common'
import { pubkey, signer } from '@marinade.finance/web3js-common'
import { verifyError } from '@marinade.finance/anchor-common'
import { initBankrunTest } from './bankrun'

describe('Staking merge verification/investigation', () => {
let provider: BankrunExtendedProvider
let program: ValidatorBondsProgram
let configAccount: PublicKey
let operatorAuthority: Keypair
const startUpEpoch = Math.floor(Math.random() * 100) + 100

beforeAll(async () => {
Expand All @@ -46,10 +58,12 @@ describe('Staking merge verification/investigation', () => {
})

beforeEach(async () => {
;({ configAccount } = await executeInitConfigInstruction({
program,
provider,
}))
;({ configAccount, operatorAuthority } = await executeInitConfigInstruction(
{
program,
provider,
}
))
})

it('cannot merge with withdrawer authority not belonging to bonds', async () => {
Expand Down Expand Up @@ -115,6 +129,7 @@ describe('Staking merge verification/investigation', () => {
sourceStakeAccount: nonDelegatedStakeAccount2,
destinationStakeAccount: nonDelegatedStakeAccount,
stakerAuthority: pubkey(staker),
settlementAccount: nonDelegatedStakeAccount,
})
try {
await provider.sendIx([], ixNonBondStaker)
Expand Down Expand Up @@ -462,7 +477,7 @@ describe('Staking merge verification/investigation', () => {
}
})

it('merging', async () => {
it('merging when funded to bond', async () => {
const [bondWithdrawer] = bondsWithdrawerAuthority(
configAccount,
program.programId
Expand Down Expand Up @@ -510,4 +525,154 @@ describe('Staking merge verification/investigation', () => {
const stakeAccount = await provider.connection.getAccountInfo(stakeAccount1)
expect(stakeAccount?.lamports).toEqual(9 * LAMPORTS_PER_SOL)
})

it('merging when funded to settlement', async () => {
const { voteAccount, validatorIdentity } = await createVoteAccount({
provider,
})
await executeInitBondInstruction({
program,
provider,
configAccount,
voteAccount,
validatorIdentity,
})
const config = await getConfig(program, configAccount)

const epoch = await currentEpoch(provider)
const maxTotalClaim = LAMPORTS_PER_SOL * 10
const { settlementAccount } = await executeInitSettlement({
configAccount,
program,
provider,
voteAccount,
operatorAuthority,
maxTotalClaim,
currentEpoch: epoch,
})

const rentExemptStake = await getRentExemptStake(provider)
const stakeAccountMinimalAmount =
rentExemptStake + config.minimumStakeLamports.toNumber()
const lamportsToFund1 = maxTotalClaim / 2 + 2 * LAMPORTS_PER_SOL
const lamportsToFund2 =
maxTotalClaim -
lamportsToFund1 +
2 * stakeAccountMinimalAmount +
1 * LAMPORTS_PER_SOL

const stakeAccount1 = await createBondsFundedStakeAccountActivated(
voteAccount,
lamportsToFund1
)
const stakeAccountData1 =
await provider.connection.getAccountInfo(stakeAccount1)
expect(stakeAccountData1?.lamports).toEqual(lamportsToFund1)
const stakeAccount2 = await createBondsFundedStakeAccountActivated(
voteAccount,
lamportsToFund2
)
const stakeAccountData2 =
await provider.connection.getAccountInfo(stakeAccount2)
expect(stakeAccountData2?.lamports).toEqual(lamportsToFund2)

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

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

let settlement = await getSettlement(program, settlementAccount)
// the amount in stake accounts were set to over-fund by 1 sol that cannot be
// split to a separate stake account and this one SOL is thus funded on top of required
expect(settlement.lamportsFunded).toEqual(
maxTotalClaim + 1 * LAMPORTS_PER_SOL
)

const [withdrawerAuthority] = bondsWithdrawerAuthority(
configAccount,
program.programId
)
const [stakerAuthority] = settlementStakerAuthority(
settlementAccount,
program.programId
)
let stakeAccount1Data = await getStakeAccount(
program.provider,
stakeAccount1,
epoch
)
const stakeAccount2Data = await getStakeAccount(
program.provider,
stakeAccount2,
epoch
)
expect(stakeAccount1Data.balanceLamports).toEqual(lamportsToFund1)
expect(stakeAccount1Data.voter).toEqual(voteAccount)
expect(stakeAccount1Data.withdrawer).toEqual(withdrawerAuthority)
expect(stakeAccount1Data.staker).toEqual(stakerAuthority)
expect(stakeAccount2Data.balanceLamports).toEqual(lamportsToFund2)
expect(stakeAccount2Data.voter).toEqual(voteAccount)
expect(stakeAccount2Data.withdrawer).toEqual(withdrawerAuthority)
expect(stakeAccount2Data.staker).toEqual(stakerAuthority)

// waiting to not getting error 0x5 of TransientState for the stake accounts
await warpToNextEpoch(provider)

const { instruction } = await mergeStakeInstruction({
program,
configAccount,
sourceStakeAccount: stakeAccount2,
destinationStakeAccount: stakeAccount1,
settlementAccount,
stakerAuthority,
})
await provider.sendIx([], instruction)

await assertNotExist(provider, stakeAccount2)
stakeAccount1Data = await getStakeAccount(
program.provider,
stakeAccount1,
epoch
)
expect(stakeAccount1Data.staker).toEqual(stakerAuthority)
expect(stakeAccount1Data.balanceLamports).toEqual(
lamportsToFund1 + lamportsToFund2
)

settlement = await getSettlement(program, settlementAccount)
expect(settlement.lamportsFunded).toEqual(
maxTotalClaim + 1 * LAMPORTS_PER_SOL
)
})

async function createBondsFundedStakeAccountActivated(
voteAccount: PublicKey,
lamports: number
): Promise<PublicKey> {
const sa = await createBondsFundedStakeAccount({
program,
provider,
voteAccount,
lamports,
configAccount,
})
await warpToNextEpoch(provider)
return sa
}
})
16 changes: 14 additions & 2 deletions packages/validator-bonds-sdk/src/instructions/mergeStake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,20 @@ export async function mergeStakeInstruction({
// stake account staker authority can be either bond managed or settlement managed
// it would be good to check settlements automatically by searching all settlements of the bond and validator
// and make sdk to find the right settlement to use when the settlement pubkey is not provided as param
stakerAuthority =
stakerAuthority ?? bondsWithdrawerAuthority(configAccount)[0]

const bondsWithdrawer = bondsWithdrawerAuthority(configAccount)[0]
if (
stakerAuthority !== undefined &&
settlementAccount.equals(PublicKey.default)
) {
if (!bondsWithdrawer.equals(stakerAuthority)) {
throw new Error(
'When stakerAuthority provided, please, provide the Settlement account address as well.' +
' Contract requires the Settlement address to derive the correct merge authority.'
)
}
}
stakerAuthority = stakerAuthority ?? bondsWithdrawer

const instruction = await program.methods
.mergeStake({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,17 @@ impl<'info> MergeStake<'info> {
merge_account_infos,
&[&[
SETTLEMENT_STAKER_AUTHORITY_SEED,
&ctx.accounts.config.key().as_ref(),
&settlement.as_ref(),
&[settlement_bump],
]],
)?
} else {
return Err(error!(ErrorCode::StakerAuthorityMismatch)
.with_account_name("staker_authority")
.with_values((
"staker_authority/bonds_withdrawer_authority/settlement_staker_authority",
"accounts.staker_authority [bonds_withdrawer_authority/settlement_staker_authority]",
format!(
"{}/{}/{}",
"{} [{}/{}]",
ctx.accounts.staker_authority.key(),
bonds_withdrawer_authority,
settlement_staker_authority
Expand Down

0 comments on commit b23f354

Please sign in to comment.