@@ -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]
197206async 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