11import { EthCheatCodes , RollupCheatCodes } from '@aztec/ethereum/test' ;
2- import { BlockNumber } from '@aztec/foundation/branded-types' ;
3- import { retryUntil } from '@aztec/foundation/retry ' ;
2+ import { SlotNumber } from '@aztec/foundation/branded-types' ;
3+ import { createLogger } from '@aztec/foundation/log ' ;
44import type { DateProvider } from '@aztec/foundation/timer' ;
5- import type { SequencerClient } from '@aztec/sequencer-client' ;
6- import type { AztecNode } from '@aztec/stdlib/interfaces/client' ;
5+ import type { AztecNode , AztecNodeDebug } from '@aztec/stdlib/interfaces/client' ;
76
87/**
98 * A class that provides utility functions for interacting with the chain.
@@ -12,6 +11,8 @@ import type { AztecNode } from '@aztec/stdlib/interfaces/client';
1211 * codes, please consider whether it makes sense to just introduce new utils in your tests instead.
1312 */
1413export class CheatCodes {
14+ private logger = createLogger ( 'aztecjs:cheat_codes' ) ;
15+
1516 constructor (
1617 /** Cheat codes for L1.*/
1718 public eth : EthCheatCodes ,
@@ -30,50 +31,55 @@ export class CheatCodes {
3031
3132 /**
3233 * Warps the L1 timestamp to a target timestamp and mines an L2 block that advances the L2 timestamp to at least
33- * the target timestamp. L2 timestamp is not advanced exactly to the target timestamp because it is determined
34- * by the slot number, which advances in fixed intervals.
35- * This is useful for testing time-dependent contract behavior.
36- * @param sequencerClient - The sequencer client to use to force an empty block to be mined.
37- * @param node - The Aztec node used to query if a new block has been mined.
34+ * the target timestamp. If the target timestamp falls within the current L2 slot (which already has a block),
35+ * the timestamp is automatically adjusted forward to the start of the next slot so that `mineBlock()` succeeds.
36+ * @param node - The Aztec node used to force an empty block to be mined.
3837 * @param targetTimestamp - The target timestamp to warp to (in seconds)
3938 */
40- async warpL2TimeAtLeastTo ( sequencerClient : SequencerClient , node : AztecNode , targetTimestamp : bigint | number ) {
41- const currentL2BlockNumber : BlockNumber = await node . getBlockNumber ( ) ;
39+ async warpL2TimeAtLeastTo ( node : AztecNodeDebug , targetTimestamp : bigint | number ) {
40+ const targetBigInt = BigInt ( targetTimestamp ) ;
41+ const currentTimestamp = BigInt ( await this . eth . lastBlockTimestamp ( ) ) ;
4242
43- // We warp the L1 timestamp
44- await this . eth . warp ( targetTimestamp , { resetBlockInterval : true } ) ;
43+ if ( targetBigInt <= currentTimestamp ) {
44+ throw new Error (
45+ `warpL2TimeAtLeastTo: target timestamp ${ targetBigInt } is not in the future (current L1 timestamp is ${ currentTimestamp } ).` ,
46+ ) ;
47+ }
4548
46- // Wait until an L2 block is mined
47- const sequencer = sequencerClient . getSequencer ( ) ;
48- const minTxsPerBlock = sequencer . getConfig ( ) . minTxsPerBlock ;
49- sequencer . updateConfig ( { minTxsPerBlock : 0 } ) ;
49+ const currentSlot = await this . rollup . getSlot ( ) ;
50+ const targetSlot = await this . rollup . getSlotAt ( targetBigInt ) ;
5051
51- await retryUntil (
52- async ( ) => {
53- const newL2BlockNumber : BlockNumber = await node . getBlockNumber ( ) ;
54- return newL2BlockNumber > currentL2BlockNumber ;
55- } ,
56- 'new block after warping L2 time' ,
57- 36 ,
58- 1 ,
59- ) ;
52+ let effectiveTimestamp = targetBigInt ;
6053
61- // Restore original minTxsPerBlock
62- sequencer . updateConfig ( { minTxsPerBlock } ) ;
54+ if ( targetSlot <= currentSlot ) {
55+ // Target lands in the same (or earlier) slot — auto-adjust to the next slot's start.
56+ const nextSlot = SlotNumber ( currentSlot + 1 ) ;
57+ const nextSlotTimestamp = await this . rollup . getTimestampForSlot ( nextSlot ) ;
58+ this . logger . warn (
59+ `warpL2TimeAtLeastTo: target timestamp ${ targetBigInt } falls in current slot ${ currentSlot } . ` +
60+ `Auto-adjusting to start of slot ${ nextSlot } at timestamp ${ nextSlotTimestamp } .` ,
61+ ) ;
62+ effectiveTimestamp = nextSlotTimestamp ;
63+ }
64+
65+ await this . eth . warp ( effectiveTimestamp , { resetBlockInterval : true } ) ;
66+ await node . mineBlock ( ) ;
6367 }
6468
6569 /**
6670 * Warps the L1 timestamp forward by a specified duration and mines an L2 block that advances the L2 timestamp at
67- * least by the duration. L2 timestamp is not advanced exactly by the duration because it is determined by the slot
68- * number, which advances in fixed intervals.
69- * This is useful for testing time-dependent contract behavior.
70- * @param sequencerClient - The sequencer client to use to force an empty block to be mined.
71- * @param node - The Aztec node used to query if a new block has been mined.
71+ * least by the duration. If the duration is too short to cross an L2 slot boundary, the warp is automatically
72+ * extended to the start of the next slot so that `mineBlock()` succeeds.
73+ * @param node - The Aztec node used to force an empty block to be mined.
7274 * @param duration - The duration to advance time by (in seconds)
7375 */
74- async warpL2TimeAtLeastBy ( sequencerClient : SequencerClient , node : AztecNode , duration : bigint | number ) {
76+ async warpL2TimeAtLeastBy ( node : AztecNodeDebug , duration : bigint | number ) {
77+ if ( BigInt ( duration ) <= 0n ) {
78+ throw new Error ( `warpL2TimeAtLeastBy: duration must be positive, got ${ duration } seconds.` ) ;
79+ }
80+
7581 const currentTimestamp = await this . eth . lastBlockTimestamp ( ) ;
7682 const targetTimestamp = BigInt ( currentTimestamp ) + BigInt ( duration ) ;
77- await this . warpL2TimeAtLeastTo ( sequencerClient , node , targetTimestamp ) ;
83+ await this . warpL2TimeAtLeastTo ( node , targetTimestamp ) ;
7884 }
7985}
0 commit comments