시간 여행

Surfpool 런타임은 세 가지 시간 여행 헬퍼를 제공합니다. 각각은 로컬 클락을 절대 목표값(상대적 오프셋이 아님)으로 이동시키며, 런타임이 요청한 시간에 실제로 도달했는지 테스트에서 검증할 수 있도록 업데이트된 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_timestampsystemClockUpdated 이벤트에 포함된 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_pauseClocksurfnet_resumeClock이 실행될 때까지 slot 생성과 시간 진행을 중단합니다 — 여러 어설션에 걸쳐 slot과 타임스탬프를 고정해야 하는 테스트에 활용하세요.

Rust SDK와 @solana/surfpoolSurfnet 클래스 모두 이 두 치트코드를 래핑하지 않습니다. 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를 반환합니다 — 일시 정지 시점과 재개 후의 클록 상태입니다. 해당 구조체에 포함되지 않는 두 가지 사항에 유의하세요:

  • 타임스탬프 없음. EpochInfo에는 시간 필드가 없습니다. 시뮬레이션된 Unix 시간은 Clock sysvar에서 읽으세요: jsonParsed 인코딩으로 SysvarC1ock11111111111111111111111111111111에 대해 getAccountInfo를 호출하면 됩니다 — 마찬가지로 초 단위입니다.
  • 일시 정지 플래그 없음. 클록이 현재 일시 정지 상태인지 보고하는 RPC 메서드는 없습니다. SDK에서는 clockUpdate 이벤트를 감시하세요. 이 이벤트는 일시 정지, 재개 또는 인터벌 변경이 실행될 때마다 clockCommand와 함께 발생합니다. 그렇지 않으면 테스트 내에서 상태를 직접 추적하세요.

일반적인 패턴

잠금 또는 베스팅 기간 테스트

아래 예제는 개략적인 예시이며, assertWithdrawFailsassertWithdrawSucceeds는 테스트 스위트에서 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마다 한 번씩 호출하세요.

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?

목차

페이지 편집