@@ -327,3 +327,237 @@ impl<P: Platform> CheckpointExt<P> for Checkpoint<P> {
327327 self . root ( ) . created_at ( )
328328 }
329329}
330+
331+ #[ cfg( test) ]
332+ mod tests {
333+ use {
334+ crate :: {
335+ alloy:: primitives:: { Address , U256 } ,
336+ payload:: { Checkpoint , CheckpointExt } ,
337+ prelude:: { BlockContext , Ethereum } ,
338+ test_utils:: { BlockContextMocked , FundedAccounts , transfer_tx} ,
339+ } ,
340+ std:: {
341+ thread,
342+ time:: { Duration , Instant } ,
343+ } ,
344+ } ;
345+
346+ #[ test]
347+ fn test_new_at_block ( ) {
348+ let block = BlockContext :: < Ethereum > :: mocked ( ) ;
349+ let cp = Checkpoint :: new_at_block ( block) ;
350+
351+ let cp2 = cp. barrier ( ) ;
352+ let cp3 = cp2. barrier ( ) ;
353+
354+ assert ! ( cp. is_empty( ) ) ;
355+ assert_eq ! ( cp2. root( ) , cp) ;
356+ assert_eq ! ( cp3. root( ) , cp) ;
357+
358+ assert_eq ! ( cp. gas_used( ) , 0 ) ;
359+ assert_eq ! ( cp. cumulative_gas_used( ) , 0 ) ;
360+
361+ assert_eq ! ( cp. effective_tip_per_gas( ) , 0 ) ;
362+ assert ! ( !cp. has_blobs( ) ) ;
363+ assert_eq ! ( cp. blob_gas_used( ) , Some ( 0 ) ) ;
364+ assert_eq ! ( cp. cumulative_blob_gas_used( ) , 0 ) ;
365+
366+ let span1 = cp3. to ( & cp) . unwrap ( ) ;
367+ let span2 = cp. to ( & cp3) . unwrap ( ) ;
368+
369+ assert_eq ! ( span1. len( ) , span2. len( ) ) ;
370+ for i in 0 ..span2. len ( ) {
371+ assert_eq ! ( span1. at( i) , span2. at( i) ) ;
372+ }
373+ assert_eq ! ( span1. len( ) , 3 ) ;
374+ assert_eq ! ( span2. len( ) , 3 ) ;
375+
376+ let addr = Address :: ZERO ;
377+ assert_eq ! ( cp. balance_of( addr) . unwrap( ) , U256 :: ZERO ) ;
378+ assert_eq ! ( cp. nonce_of( addr) . unwrap( ) , 0 ) ;
379+
380+ assert ! ( cp. signers( ) . is_empty( ) ) ;
381+ assert ! ( cp. nonces( ) . is_empty( ) ) ;
382+
383+ assert_eq ! ( cp. hash( ) , None ) ;
384+ assert ! ( !cp. is_bundle( ) ) ;
385+ assert ! ( !cp. has_failures( ) ) ;
386+ assert_eq ! ( cp. failed_txs( ) . count( ) , 0 ) ;
387+
388+ let random_addr = Address :: random ( ) ;
389+ assert_eq ! (
390+ cp. balance_of( random_addr) . unwrap( ) ,
391+ U256 :: ZERO ,
392+ "Nonexistent account should have zero balance"
393+ ) ;
394+
395+ assert_eq ! (
396+ cp. nonce_of( random_addr) . unwrap( ) ,
397+ 0 ,
398+ "Nonexistent account should have zero nonce"
399+ ) ;
400+ }
401+
402+ #[ test]
403+ fn test_contains_is_false_without_txs ( ) {
404+ let block = BlockContext :: < Ethereum > :: mocked ( ) ;
405+ let cp1 = Checkpoint :: new_at_block ( block) ;
406+
407+ let tx1 = transfer_tx ( & FundedAccounts :: signer ( 0 ) , 0 , U256 :: from ( 50_000u64 ) ) ;
408+ let tx1_hash = * tx1. hash ( ) ;
409+ assert ! ( !cp1. contains( tx1_hash) ) ;
410+ let cp2 = cp1. apply ( tx1) . unwrap ( ) ;
411+
412+ assert ! ( cp2. contains( tx1_hash) ) ;
413+ }
414+
415+ #[ test]
416+ fn test_history_timestamps ( ) {
417+ let block = BlockContext :: < Ethereum > :: mocked ( ) ;
418+ let cp1 = Checkpoint :: new_at_block ( block) ;
419+
420+ thread:: sleep ( Duration :: from_millis ( 5 ) ) ;
421+
422+ let cp2 = cp1. barrier ( ) ;
423+
424+ assert ! ( cp2. building_since( ) <= Instant :: now( ) ) ;
425+ assert ! ( cp2. building_since( ) >= cp1. created_at( ) ) ;
426+ }
427+
428+ #[ test]
429+ fn test_to_self ( ) {
430+ let block = BlockContext :: < Ethereum > :: mocked ( ) ;
431+ let cp = Checkpoint :: new_at_block ( block) ;
432+
433+ // to(self, self) should produce a span of length 1 containing the
434+ // checkpoint itself
435+ let span = cp. to ( & cp) . expect ( "to(self,self) must succeed" ) ;
436+ assert_eq ! ( span. len( ) , 1 ) ;
437+ assert_eq ! ( * span. at( 0 ) . unwrap( ) , cp) ;
438+ }
439+
440+ #[ test]
441+ fn test_to_non_linear_error ( ) {
442+ let block_a = BlockContext :: < Ethereum > :: mocked ( ) ;
443+ let block_b = BlockContext :: < Ethereum > :: mocked ( ) ;
444+
445+ let cp_a = Checkpoint :: new_at_block ( block_a) ;
446+ let cp_b = Checkpoint :: new_at_block ( block_b) ;
447+
448+ // They are not on the same linear history, so to should return an Err.
449+ assert ! ( cp_a. to( & cp_b) . is_err( ) ) ;
450+ assert ! ( cp_b. to( & cp_a) . is_err( ) ) ;
451+ }
452+
453+ #[ test]
454+ fn test_to_includes_all_intermediates_and_is_linear ( ) {
455+ let block = BlockContext :: < Ethereum > :: mocked ( ) ;
456+ let base = Checkpoint :: new_at_block ( block) ;
457+
458+ // base -> x -> y
459+ let tx_x = transfer_tx ( & FundedAccounts :: signer ( 0 ) , 0 , U256 :: from ( 10u64 ) ) ;
460+ let x = base. apply ( tx_x) . unwrap ( ) ;
461+
462+ let tx_y = transfer_tx ( & FundedAccounts :: signer ( 1 ) , 0 , U256 :: from ( 20u64 ) ) ;
463+ let y = x. apply ( tx_y) . unwrap ( ) ;
464+
465+ let x_barrier = x. barrier ( ) ;
466+ let y_barrier = y. barrier ( ) ;
467+
468+ // `to` between base and y_barrier should include `base``, `x` (or
469+ // x_barrier), `y` (or y_barrier)
470+ let span_by = y_barrier
471+ . to ( & base)
472+ . expect ( "to should succeed for linear history" ) ;
473+ let collected: Vec < Checkpoint < Ethereum > > = ( 0 ..span_by. len ( ) )
474+ . map ( |i| span_by. at ( i) . unwrap ( ) . clone ( ) )
475+ . collect ( ) ;
476+
477+ assert ! ( collected. contains( & base) ) ;
478+ assert ! ( collected. contains( & y_barrier) ) ;
479+ assert ! ( collected. iter( ) . any( |cp| * cp == x || * cp == x_barrier) ) ;
480+ }
481+
482+ #[ test]
483+ fn test_to_different_roots_error ( ) {
484+ let block1 = BlockContext :: < Ethereum > :: mocked ( ) ;
485+ let block2 = BlockContext :: < Ethereum > :: mocked ( ) ;
486+
487+ let root1 = Checkpoint :: new_at_block ( block1) ;
488+ let root2 = Checkpoint :: new_at_block ( block2) ;
489+
490+ let tx = transfer_tx ( & FundedAccounts :: signer ( 0 ) , 0 , U256 :: from ( 5u64 ) ) ;
491+ let root1_child = root1. apply ( tx) . unwrap ( ) ;
492+
493+ assert ! ( root1_child. to( & root2) . is_err( ) ) ;
494+ assert ! ( root2. to( & root1_child) . is_err( ) ) ;
495+ }
496+
497+ #[ test]
498+ fn test_effective_tip_checkpoint ( ) {
499+ let block = BlockContext :: < Ethereum > :: mocked ( ) ;
500+ let cp = Checkpoint :: new_at_block ( block) ;
501+ assert_eq ! (
502+ cp. effective_tip_per_gas( ) ,
503+ 0 ,
504+ "Empty checkpoint should have zero tip"
505+ ) ;
506+
507+ let tx = transfer_tx ( & FundedAccounts :: signer ( 0 ) , 0 , U256 :: from ( 100u64 ) ) ;
508+ let cp2 = cp. apply ( tx. clone ( ) ) . unwrap ( ) ;
509+
510+ let tip = cp2. effective_tip_per_gas ( ) ;
511+ assert ! ( tip > 0 , "Transaction should have positive effective tip" ) ;
512+ }
513+
514+ #[ test]
515+ fn test_history_staging_no_barrier ( ) {
516+ let block = BlockContext :: < Ethereum > :: mocked ( ) ;
517+ let base = Checkpoint :: new_at_block ( block) ;
518+
519+ let tx1 = transfer_tx ( & FundedAccounts :: signer ( 0 ) , 0 , U256 :: from ( 50u64 ) ) ;
520+ let cp1 = base. apply ( tx1) . unwrap ( ) ;
521+
522+ let tx2 = transfer_tx ( & FundedAccounts :: signer ( 1 ) , 0 , U256 :: from ( 75u64 ) ) ;
523+ let cp2 = cp1. apply ( tx2) . unwrap ( ) ;
524+
525+ let staging = cp2. history_staging ( ) ;
526+ let full = cp2. history ( ) ;
527+
528+ assert_eq ! (
529+ staging. len( ) ,
530+ full. len( ) ,
531+ "Without barriers, staging should equal full history"
532+ ) ;
533+ }
534+
535+ #[ test]
536+ fn test_history_staging_with_barrier ( ) {
537+ let block = BlockContext :: < Ethereum > :: mocked ( ) ;
538+ let base = Checkpoint :: new_at_block ( block) ;
539+
540+ let tx1 = transfer_tx ( & FundedAccounts :: signer ( 0 ) , 0 , U256 :: from ( 50u64 ) ) ;
541+ let cp1 = base. apply ( tx1) . unwrap ( ) ;
542+
543+ let barrier = cp1. barrier ( ) ;
544+
545+ let tx2 = transfer_tx ( & FundedAccounts :: signer ( 1 ) , 0 , U256 :: from ( 75u64 ) ) ;
546+ let cp2 = barrier. apply ( tx2) . unwrap ( ) ;
547+
548+ let staging = cp2. history_staging ( ) ;
549+
550+ // Staging should only include checkpoints after the barrier
551+ assert ! (
552+ staging. len( ) < cp2. history( ) . len( ) ,
553+ "Staging should be shorter than full history"
554+ ) ;
555+ let sealed = cp2. history_sealed ( ) ;
556+
557+ // Sealed should include everything up to and including the barrier
558+ assert ! (
559+ !sealed. is_empty( ) ,
560+ "Sealed should include checkpoints up to barrier"
561+ ) ;
562+ }
563+ }
0 commit comments