Skip to content

Commit 3d2bb28

Browse files
committed
program: test replenish for sub-minimum delegation
* test moving less than the minimum to onramp adds to activating but does not attempt to delegate inactive * test the no-op cases successfully no-op * use unique value for existing lamports in onramp
1 parent 39102e9 commit 3d2bb28

1 file changed

Lines changed: 128 additions & 41 deletions

File tree

program/tests/replenish.rs

Lines changed: 128 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -182,29 +182,32 @@ enum OnRampState {
182182
Deactive,
183183
}
184184

185-
// initialized/move: onramp is fresh from its last replenish, we can move from pool and delegate
186-
// activating/no move: onramp has loose lamports, we can add to its delegation
187-
// activating/move: onramp has loose lamports, we can add them and pool lamports both to delegation
188-
// active/no move: onramp stake can be moved back into pool
189-
// active/move: onramp stake can be moved back into pool *and* pool lamports can be delegated in onramp
190-
// deactive/move: same as the initialized case, but if onramp was deactivated
185+
#[derive(Clone, Copy, PartialEq, Eq)]
186+
enum MoveLamportsCase {
187+
None,
188+
SubMinumumDelegation,
189+
MinimumDelegation,
190+
}
191+
192+
// initialized/move: onramp is fresh from its last replenish, move lamports from pool, and delegate if minimum
193+
// activating/no move: onramp has loose lamports, add them to its delegation unconditionally
194+
// activating/move: pool has loose lamports, move and add them to onramp delegation unconditionally
195+
// active/no move: move onramp stake to pool, onramp ends initialized with rent exemption only
196+
// active/move: move onramp stake to pool, move lamports from pool, and delegate if minimum
197+
// deactive/move: same as the initialized/move case, but if onramp was deactivated
198+
// initialized/no move and deactive/no move: nothing happens. successfully!
199+
// ALL variants return Ok(()). replenish is a "magic" instruction that only performs stake operations which should succeed
191200
#[test_matrix(
192201
[StakeProgramVersion::Stable, StakeProgramVersion::Beta, StakeProgramVersion::Edge],
193202
[OnRampState::Initialized, OnRampState::Activating, OnRampState::Active, OnRampState::Deactive],
194-
[false, true]
203+
[MoveLamportsCase::None, MoveLamportsCase::SubMinumumDelegation, MoveLamportsCase::MinimumDelegation]
195204
)]
196205
#[tokio::test]
197206
async fn move_value_success(
198207
stake_version: StakeProgramVersion,
199208
onramp_state: OnRampState,
200-
move_lamports_to_onramp: bool,
209+
move_lamports_case: MoveLamportsCase,
201210
) {
202-
// these cases would not attempt to move value in either direction
203-
match (onramp_state, move_lamports_to_onramp) {
204-
(OnRampState::Initialized, false) | (OnRampState::Deactive, false) => return,
205-
_ => (),
206-
};
207-
208211
let Some(program_test) = program_test(stake_version) else {
209212
return;
210213
};
@@ -228,6 +231,15 @@ async fn move_value_success(
228231
)
229232
.await;
230233

234+
// if we dont move, we dont move; instruction succeeds
235+
// if we move less than minimum, we move and dont delegate; instruction succeeds
236+
// if we move the minimum, we move and delegate; instruction succeeds
237+
let move_lamports_to_onramp = match move_lamports_case {
238+
MoveLamportsCase::None => None,
239+
MoveLamportsCase::SubMinumumDelegation => Some(minimum_delegation - 1),
240+
MoveLamportsCase::MinimumDelegation => Some(minimum_delegation),
241+
};
242+
231243
let minimum_pool_balance = get_minimum_pool_balance(
232244
&mut context.banks_client,
233245
&context.payer,
@@ -260,28 +272,43 @@ async fn move_value_success(
260272
}
261273

262274
// if we are testing the pool -> onramp leg, add lamports for it
263-
if move_lamports_to_onramp {
275+
if let Some(move_lamports_amount) = move_lamports_to_onramp {
264276
transfer(
265277
&mut context.banks_client,
266278
&context.payer,
267279
&context.last_blockhash,
268280
&accounts.stake_account,
269-
minimum_delegation,
281+
move_lamports_amount,
270282
)
271283
.await;
272-
}
284+
};
273285

274-
// this one case is to test reupping an activating delegation
275-
if onramp_state == OnRampState::Activating && !move_lamports_to_onramp {
276-
transfer(
277-
&mut context.banks_client,
278-
&context.payer,
279-
&context.last_blockhash,
280-
&accounts.onramp_account,
281-
minimum_delegation,
282-
)
283-
.await;
284-
}
286+
// this one case is to test reupping an activating delegation in place
287+
let existing_onramp_lamports =
288+
if onramp_state == OnRampState::Activating && move_lamports_to_onramp.is_none() {
289+
// NOTE we want a unique value to be sure our test cases are right and this didnt come from pool.
290+
// we also prefer sub-minimal, so we know we can add sub-minimal lamports to an already sufficient activation.
291+
// however stake v4 minimum is 1. this comment can be removed when stake v5 is Stable,
292+
// and the value can be changed to simply `minimum_delegation - 2`
293+
let existing_onramp_lamports = if minimum_delegation == 1 {
294+
minimum_delegation + 2
295+
} else {
296+
minimum_delegation - 2
297+
};
298+
299+
transfer(
300+
&mut context.banks_client,
301+
&context.payer,
302+
&context.last_blockhash,
303+
&accounts.onramp_account,
304+
existing_onramp_lamports,
305+
)
306+
.await;
307+
308+
existing_onramp_lamports
309+
} else {
310+
0
311+
};
285312

286313
// this is the replenish we test
287314
replenish(&mut context, &accounts.vote_account.pubkey()).await;
@@ -312,7 +339,8 @@ async fn move_value_success(
312339

313340
match (onramp_state, move_lamports_to_onramp) {
314341
// stake moved already before test or because of test, new lamports were added to onramp
315-
(OnRampState::Deactive, true) | (OnRampState::Active, true) => {
342+
(OnRampState::Deactive, Some(move_lamports_amount))
343+
| (OnRampState::Active, Some(move_lamports_amount)) => {
316344
assert_eq!(
317345
pool_status.effective,
318346
minimum_pool_balance + minimum_delegation
@@ -323,20 +351,34 @@ async fn move_value_success(
323351
);
324352

325353
assert_eq!(onramp_status.effective, 0);
326-
assert_eq!(onramp_status.activating, minimum_delegation);
327-
assert_eq!(onramp_lamports, minimum_delegation + onramp_rent);
354+
assert_eq!(
355+
onramp_status.activating,
356+
if move_lamports_amount >= minimum_delegation {
357+
move_lamports_amount
358+
} else {
359+
0
360+
}
361+
);
362+
assert_eq!(onramp_lamports, move_lamports_amount + onramp_rent);
328363
}
329364
// no stake moved, but lamports did
330-
(OnRampState::Initialized, true) => {
365+
(OnRampState::Initialized, Some(move_lamports_amount)) => {
331366
assert_eq!(pool_status.effective, minimum_pool_balance);
332367
assert_eq!(pool_lamports, minimum_pool_balance + pool_rent);
333368

334369
assert_eq!(onramp_status.effective, 0);
335-
assert_eq!(onramp_status.activating, minimum_delegation);
336-
assert_eq!(onramp_lamports, minimum_delegation + onramp_rent);
370+
assert_eq!(
371+
onramp_status.activating,
372+
if move_lamports_amount >= minimum_delegation {
373+
move_lamports_amount
374+
} else {
375+
0
376+
}
377+
);
378+
assert_eq!(onramp_lamports, move_lamports_amount + onramp_rent);
337379
}
338380
// no excess lamports moved, just stake
339-
(OnRampState::Active, false) => {
381+
(OnRampState::Active, None) => {
340382
assert_eq!(
341383
pool_status.effective,
342384
minimum_pool_balance + minimum_delegation
@@ -350,17 +392,62 @@ async fn move_value_success(
350392
assert_eq!(onramp_status.activating, 0);
351393
assert_eq!(onramp_lamports, onramp_rent);
352394
}
353-
// topped up an existing activation, either with pool or onramp lamports
354-
(OnRampState::Activating, _) => {
395+
// topped up an existing activation by moving from pool
396+
(OnRampState::Activating, Some(move_lamports_amount)) => {
355397
assert_eq!(pool_status.effective, minimum_pool_balance);
356398
assert_eq!(pool_lamports, minimum_pool_balance + pool_rent);
357399

358400
assert_eq!(onramp_status.effective, 0);
359-
assert_eq!(onramp_status.activating, minimum_delegation * 2);
360-
assert_eq!(onramp_lamports, minimum_delegation * 2 + onramp_rent);
401+
assert_eq!(
402+
onramp_status.activating,
403+
move_lamports_amount + minimum_delegation
404+
);
405+
assert_eq!(
406+
onramp_lamports,
407+
move_lamports_amount + minimum_delegation + onramp_rent
408+
);
409+
}
410+
// topped up an existing activation with lamports already in onramp
411+
(OnRampState::Activating, None) => {
412+
assert!(existing_onramp_lamports > 0);
413+
414+
assert_eq!(pool_status.effective, minimum_pool_balance);
415+
assert_eq!(pool_lamports, minimum_pool_balance + pool_rent);
416+
417+
assert_eq!(onramp_status.effective, 0);
418+
assert_eq!(
419+
onramp_status.activating,
420+
existing_onramp_lamports + minimum_delegation
421+
);
422+
assert_eq!(
423+
onramp_lamports,
424+
existing_onramp_lamports + minimum_delegation + onramp_rent
425+
);
426+
}
427+
// nothing happened
428+
(OnRampState::Initialized, None) => {
429+
assert_eq!(pool_status.effective, minimum_pool_balance);
430+
assert_eq!(pool_lamports, minimum_pool_balance + pool_rent);
431+
432+
assert_eq!(onramp_status.effective, 0);
433+
assert_eq!(onramp_status.activating, 0);
434+
assert_eq!(onramp_lamports, onramp_rent);
435+
}
436+
// still nothing happened but setting up a deactive onramp added stake to pool already
437+
(OnRampState::Deactive, None) => {
438+
assert_eq!(
439+
pool_status.effective,
440+
minimum_delegation + minimum_pool_balance
441+
);
442+
assert_eq!(
443+
pool_lamports,
444+
minimum_delegation + minimum_pool_balance + pool_rent
445+
);
446+
447+
assert_eq!(onramp_status.effective, 0);
448+
assert_eq!(onramp_status.activating, 0);
449+
assert_eq!(onramp_lamports, onramp_rent);
361450
}
362-
// we have no further test cases
363-
_ => unreachable!(),
364451
}
365452
}
366453

0 commit comments

Comments
 (0)