Viaggio nel Tempo

Il runtime di Surfpool espone tre helper per il viaggio nel tempo. Ognuno sposta il clock locale verso un obiettivo assoluto (non un offset relativo) e restituisce il valore aggiornato EpochInfo così i test possono verificare che il runtime abbia effettivamente raggiunto il tempo richiesto.

HelperObiettivoRestituisce
time_travel_to_slot / timeTravelToSlotNumero di slot assolutoEpochInfo con il nuovo absolute_slot
time_travel_to_epoch / timeTravelToEpochNumero di epoch assolutoEpochInfo con il nuovo epoch
time_travel_to_timestamp / timeTravelToTimestampTimestamp Unix in millisecondiEpochInfo che riflette lo slot implicito

Solo in avanti

Il viaggio nel tempo può spostare l'orologio solo in avanti. Un target nel passato viene rifiutato anziché ignorato — gli helper Rust restituiscono un Err e gli helper JS generano un'eccezione. Il messaggio indica quale unità è andata a ritroso, ad esempio Cannot travel to past slot: target=1000, current=2000.

Salta a uno Slot

use surfpool_sdk::Surfnet;
let surfnet = Surfnet::start().await?;
let cheats = surfnet.cheatcodes();
let info = cheats.time_travel_to_slot(1_000_000)?;
assert!(info.absolute_slot >= 1_000_000);

Salta a un Epoch

let info = cheats.time_travel_to_epoch(420)?;
assert_eq!(info.epoch, 420);

Salta a un Timestamp Unix

Passa il timestamp in millisecondi dall'epoch Unix — la stessa unità di Date.now() in JavaScript — non in secondi. Il runtime calcola lo slot più vicino a quel timestamp.

// 2030-01-01T00:00:00Z
let info = cheats.time_travel_to_timestamp(1_893_456_000_000)?;

L'orologio riporta i secondi, non i millisecondi

Ogni timestamp riletto dal runtime è in secondi — il unix_timestamp della sysvar Clock on-chain e il ClockValue trasportato dall'evento systemClockUpdated. Passare direttamente uno di questi valori a un helper di viaggio nel tempo risolve a un momento dell'inizio del 1970, quindi la chiamata viene rifiutata come target nel passato. Moltiplica prima per 1000.

// WRONG — `unixTimestamp` is in seconds, so this is a past target.
surfnet.timeTravelToTimestamp(clock.unixTimestamp + 3600);
// RIGHT — convert to milliseconds.
surfnet.timeTravelToTimestamp((clock.unixTimestamp + 3600) * 1000);

Metti in Pausa e Riprendi l'Orologio

Il viaggio nel tempo salta a un target; la pausa arresta completamente l'avanzamento dell'orologio. surfnet_pauseClock blocca la produzione di slot e il progresso del tempo fino a quando non viene eseguito surfnet_resumeClock — usalo quando un test necessita che lo slot e il timestamp rimangano fermi durante più asserzioni.

Né l'SDK Rust né la classe Surfnet in @solana/surfpool racchiudono questi due cheatcode. Invocali tramite il plugin Kit, oppure direttamente via JSON-RPC.

const paused = await client.cheatcodes.pauseClock().send();
// Nothing advances until the clock is resumed, so this is the only thing that
// moves the slot.
await client.cheatcodes
.timeTravel({ absoluteSlot: paused.absoluteSlot + 1_000n })
.send();
await client.cheatcodes.resumeClock().send();

Entrambi i cheatcode restituiscono un EpochInfo — lo stato dell'orologio al momento della pausa e dopo la ripresa. Nota le due cose che quella struttura non contiene:

  • Nessun timestamp. EpochInfo non ha un campo temporale. Leggi il tempo Unix simulato dalla sysvar Clock: getAccountInfo su SysvarC1ock11111111111111111111111111111111 con encoding jsonParsed — sempre in secondi.
  • Nessun flag di pausa. Nessun metodo RPC riporta se l'orologio è attualmente in pausa. Tramite gli SDK, monitora l'evento clockUpdate, che si attiva con un clockCommand ogni volta che viene eseguita una pausa, una ripresa o una modifica dell'intervallo. In alternativa, tieni traccia dello stato nel tuo test.

Pattern Comuni

Testa un Blocco o una Finestra di Vesting

L'esempio seguente è uno schema — assertWithdrawFails e assertWithdrawSucceeds sono segnaposto per gli helper lato client che la tua suite di test utilizza per verificare il comportamento RPC.

import { Surfnet } from "@solana/surfpool";
const surfnet = Surfnet.start();
const beneficiary = Surfnet.newKeypair();
// 1. Set up a vesting account that unlocks at slot 1,000,000.
surfnet.setAccount(/* ...lockup program state... */);
// 2. Verify withdrawal fails before unlock.
await assertWithdrawFails(surfnet.rpcUrl, beneficiary);
// 3. Travel past the unlock slot.
surfnet.timeTravelToSlot(1_000_001);
// 4. Verify withdrawal succeeds.
await assertWithdrawSucceeds(surfnet.rpcUrl, beneficiary);
surfnet.stop();

Gestire uno Scenario Multi-Epoch

Gli slot intermedi vengono saltati

Il salto temporale va direttamente alla destinazione — spostandosi dall'epoch 1 all'epoch 5 vengono saltati gli slot intermedi. Se il tuo programma necessita che ogni confine di epoch intermedio venga attivato (ad esempio, per accreditare le ricompense per epoch), chiama time_travel_to_epoch una volta per epoch con le eventuali transazioni necessarie nel mezzo.

for epoch in 2..=5 {
cheats.time_travel_to_epoch(epoch)?;
surfnet.rpc_client().send_transaction(&claim_rewards_tx)?;
}

Struttura di EpochInfo

Entrambi gli SDK restituiscono un oggetto di tipo EpochInfo con questi campi:

CampoRustJS
Slot assolutoabsolute_slot: u64absoluteSlot: number
Slot nell'epochslot_index: u64slotIndex: number
Slot per epochslots_in_epoch: u64slotsInEpoch: number
Numero epochepoch: u64epoch: number
Altezza bloccoblock_height: u64blockHeight: number
Conteggio transazionitransaction_count: Option<u64>transactionCount?: number

Is this page helpful?

Indice dei contenuti

Modifica pagina
© 2026 Solana Foundation. Tutti i diritti riservati.