タイムトラベル

Surfpoolランタイムは3つのタイムトラベルヘルパーを提供します。それぞれのヘルパーはローカルクロックを絶対ターゲット(相対オフセットではなく)に移動させ、更新されたEpochInfoを返すため、テストはランタイムが実際にリクエストされた時刻に到達したことを検証できます。

ヘルパーターゲット返り値
time_travel_to_slot / timeTravelToSlot絶対slot番号新しいabsolute_slotを含むEpochInfo
time_travel_to_epoch / timeTravelToEpoch絶対epoch番号新しいepochを含むEpochInfo
time_travel_to_timestamp / timeTravelToTimestampミリ秒単位のUnixタイムスタンプ対応するslotを反映したEpochInfo

前方方向のみ

タイムトラベルはクロックを前方にのみ移動できます。過去のターゲットは無視されるのではなく、拒否されます — RustヘルパーはErrを返し、JSヘルパーはスローします。メッセージは、どのユニットが後退したかを示します。例:Cannot travel to past slot: target=1000, current=2000

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);

epochへジャンプ

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

Unixタイムスタンプへジャンプ

タイムスタンプはUnix epochからのミリ秒で渡してください — JavaScriptのDate.now()と同じ単位であり、秒ではありません。ランタイムはそのタイムスタンプに最も近いslotを計算します。

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

クロックはミリ秒ではなく秒を報告します

ランタイムから読み返すタイムスタンプはすべて秒単位です — オンチェーンのClock sysvarのunix_timestamp、およびsystemClockUpdatedイベントが持つClockValueも同様です。これらの値をそのままタイムトラベルヘルパーに渡すと、1970年代初頭の時点に解決されるため、過去のターゲットとして拒否されます。先に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);

クロックの一時停止と再開

タイムトラベルはターゲットにジャンプしますが、一時停止はクロックの進行を完全に停止します。surfnet_pauseClockはslotの生成と時間の進行を停止し、surfnet_resumeClockが実行されるまで維持します — テストで複数のアサションにわたってslotとタイムスタンプを固定したい場合に使用してください。

Rust SDKも@solana/surfpoolSurfnetクラスも、これら2つのチートコードをラップしていません。Kitプラグインを通じて、または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();

どちらのチートコードもEpochInfoを返します — 一時停止時点および再開後のクロック状態です。その型が持たない2つの点に注意してください:

  • タイムスタンプなし。EpochInfoには時刻フィールドがありません。シミュレートされたUnix時刻は、代わりにClock sysvarから読み取ってください:jsonParsedエンコーディングを使用してSysvarC1ock11111111111111111111111111111111に対してgetAccountInfoを呼び出します — こちらも秒単位です。
  • **一時停止フラグなし。**クロックが現在一時停止中かどうかを報告するRPCメソッドはありません。SDKからはclockUpdateイベントを監視してください。このイベントは、一時停止、再開、またはインターバル変更が実行されるたびにclockCommandとともに発火します。それ以外の場合は、テスト内で状態を追跡してください。

よくある使用パターン

ロックアップまたはベスティングウィンドウのテスト

以下の例はスケッチです — assertWithdrawFailsおよびassertWithdrawSucceedsは、テストスイートで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();

マルチepochシナリオを実行する

中間のslotはスキップされます

タイムトラベルはターゲットに直接ジャンプします — epoch 1からepoch 5に移動すると、 その間のslotはスキップされます。プログラムが各中間epoch の境界での処理を必要とする場合(たとえば、epochごとの報酬を付与するなど)、 間に必要なトランザクションを挟みながら、time_travel_to_epochをepochごとに1回呼び出してください。

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

EpochInfoの構造

両方のSDKはこれらのフィールドを持つEpochInfo形式のオブジェクトを返します:

フィールドRustJS
絶対slotabsolute_slot: u64absoluteSlot: number
epoch内のslotslot_index: u64slotIndex: number
epoch当たりのslot数slots_in_epoch: u64slotsInEpoch: number
epoch番号epoch: u64epoch: number
ブロック高block_height: u64blockHeight: number
トランザクション数transaction_count: Option<u64>transactionCount?: number

Is this page helpful?

© 2026 Solana Foundation. 無断転載を禁じます。