Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"@dfinity/identity": "^3.2.4",
"@dfinity/principal": "^3.2.4",
"bip39": "^3.1.0",
"json-with-bigint": "3.4.4",
},
},
"packages/pic-server": {
Expand Down Expand Up @@ -787,6 +788,8 @@

"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],

"json-with-bigint": ["json-with-bigint@3.4.4", "", {}, "sha512-AhpYAAaZsPjU7smaBomDt1SOQshi9rEm6BlTbfVwsG1vNmeHKtEedJi62sHZzJTyKNtwzmNnrsd55kjwJ7054A=="],

"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],

"kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
Expand Down
2 changes: 1 addition & 1 deletion examples/clock/tests/src/clock.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('Clock', () => {
it('should set and get canister cycles', async () => {
const cycles = await pic.getCyclesBalance(canisterId);

const cyclesToAdd = 1_000_000_000;
const cyclesToAdd = BigInt(1_000_000_000);
const updatedCyclesBalance = await pic.addCycles(canisterId, cyclesToAdd);
const fetchUpdatedCyclesBalance = await pic.getCyclesBalance(canisterId);

Expand Down
3 changes: 2 additions & 1 deletion packages/pic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@dfinity/candid": "^3.2.4",
"@dfinity/identity": "^3.2.4",
"@dfinity/principal": "^3.2.4",
"bip39": "^3.1.0"
"bip39": "^3.1.0",
"json-with-bigint": "3.4.4"
}
}
17 changes: 12 additions & 5 deletions packages/pic/src/http2-client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { JSONStringify } from 'json-with-bigint';
import { ServerRequestTimeoutError } from './error';
import { isNil, poll } from './util';

Expand All @@ -13,12 +14,14 @@ export type RequestHeaders = RequestInit['headers'];
export interface JsonGetRequest {
path: string;
headers?: RequestHeaders;
responseJsonParser?: typeof JSON.parse;
}

export interface JsonPostRequest<B> {
path: string;
headers?: RequestHeaders;
body?: B;
responseJsonParser?: typeof JSON.parse;
}

export type ResponseHeaders = ResponseInit['headers'];
Expand Down Expand Up @@ -71,6 +74,7 @@ export class Http2Client {
}

public async jsonGet<R extends {}>(init: JsonGetRequest): Promise<R> {
const responseJsonParser = init.responseJsonParser ?? JSON.parse;
// poll the request until it is successful or times out
return await poll(
async () => {
Expand All @@ -80,7 +84,7 @@ export class Http2Client {
headers: { ...init.headers, ...JSON_HEADER },
});

const resBody = await getResBody<R>(res);
const resBody = await getResBody<R>(res, responseJsonParser);
if (isNil(resBody)) {
return resBody;
}
Expand Down Expand Up @@ -120,8 +124,9 @@ export class Http2Client {
}

public async jsonPost<B, R extends {}>(init: JsonPostRequest<B>): Promise<R> {
const responseJsonParser = init.responseJsonParser ?? JSON.parse;
const reqBody = init.body
? new TextEncoder().encode(JSON.stringify(init.body))
? new TextEncoder().encode(JSONStringify(init.body))
: undefined;

// poll the request until it is successful or times out
Expand All @@ -134,7 +139,7 @@ export class Http2Client {
body: reqBody,
});

const resBody = await getResBody<R>(res);
const resBody = await getResBody<R>(res, responseJsonParser);
if (isNil(resBody)) {
return resBody;
}
Expand Down Expand Up @@ -200,11 +205,13 @@ export class Http2Client {

async function getResBody<R extends {}>(
res: Response,
jsonParser: typeof JSON.parse,
): Promise<ApiResponse<R>> {
const resBody = await res.text();
try {
return (await res.clone().json()) as ApiResponse<R>;
return jsonParser(resBody) as ApiResponse<R>;
} catch (error) {
const message = await res.text();
const message = resBody;

console.error('Error parsing PocketIC server response body:', error);
console.error('Original body:', message);
Expand Down
40 changes: 26 additions & 14 deletions packages/pic/src/pocket-ic-client-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
} from './util';
import { TopologyValidationError } from './error';

const NANOS_PER_MILLISECOND = BigInt(1_000_000);

//#region CreateInstance

export interface CreateInstanceRequest {
Expand Down Expand Up @@ -495,38 +497,48 @@ export function decodeGetControllersResponse(
//#region GetTime

export interface GetTimeResponse {
millisSinceEpoch: number;
nanosSinceEpoch: bigint;
}

export interface EncodedGetTimeResponse {
nanos_since_epoch: number;
nanos_since_epoch: bigint;
}

export function decodeGetTimeResponse(
res: EncodedGetTimeResponse,
): GetTimeResponse {
return {
millisSinceEpoch: res.nanos_since_epoch / 1_000_000,
nanosSinceEpoch: res.nanos_since_epoch,
};
}

//#endregion GetTime

//#region SetTime

export interface SetTimeRequest {
millisSinceEpoch: number;
}
export type SetTimeRequest =
| {
millisSinceEpoch: number;
}
| {
nanosSinceEpoch: bigint;
};

export interface EncodedSetTimeRequest {
nanos_since_epoch: number;
nanos_since_epoch: bigint;
}

export function encodeSetTimeRequest(
req: SetTimeRequest,
): EncodedSetTimeRequest {
if ('millisSinceEpoch' in req) {
return {
nanos_since_epoch: BigInt(req.millisSinceEpoch) * NANOS_PER_MILLISECOND,
};
}

return {
nanos_since_epoch: req.millisSinceEpoch * 1_000_000,
nanos_since_epoch: req.nanosSinceEpoch,
};
}

Expand Down Expand Up @@ -595,11 +607,11 @@ export function encodeGetCyclesBalanceRequest(
}

export interface EncodedGetCyclesBalanceResponse {
cycles: number;
cycles: bigint;
}

export interface GetCyclesBalanceResponse {
cycles: number;
cycles: bigint;
}

export function decodeGetCyclesBalanceResponse(
Expand All @@ -616,12 +628,12 @@ export function decodeGetCyclesBalanceResponse(

export interface AddCyclesRequest {
canisterId: Principal;
amount: number;
amount: bigint;
}

export interface EncodedAddCyclesRequest {
canister_id: string;
amount: number;
amount: bigint;
}

export function encodeAddCyclesRequest(
Expand All @@ -634,11 +646,11 @@ export function encodeAddCyclesRequest(
}

export interface AddCyclesResponse {
cycles: number;
cycles: bigint;
}

export interface EncodedAddCyclesResponse {
cycles: number;
cycles: bigint;
}

export function decodeAddCyclesResponse(
Expand Down
23 changes: 18 additions & 5 deletions packages/pic/src/pocket-ic-client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { JSONParse } from 'json-with-bigint';
import { Http2Client } from './http2-client';
import {
EncodedAddCyclesRequest,
Expand Down Expand Up @@ -171,7 +172,10 @@ export class PocketIcClient {
public async getTime(): Promise<GetTimeResponse> {
this.assertInstanceNotDeleted();

const res = await this.get<EncodedGetTimeResponse>('/read/get_time');
const res = await this.get<EncodedGetTimeResponse>(
'/read/get_time',
JSONParse,
);

return decodeGetTimeResponse(res);
}
Expand Down Expand Up @@ -215,7 +219,7 @@ export class PocketIcClient {
const res = await this.post<
EncodedGetCyclesBalanceRequest,
EncodedGetCyclesBalanceResponse
>('/read/get_cycles', encodeGetCyclesBalanceRequest(req));
>('/read/get_cycles', encodeGetCyclesBalanceRequest(req), JSONParse);

return decodeGetCyclesBalanceResponse(res);
}
Expand All @@ -226,7 +230,7 @@ export class PocketIcClient {
const res = await this.post<
EncodedAddCyclesRequest,
EncodedAddCyclesResponse
>('/update/add_cycles', encodeAddCyclesRequest(req));
>('/update/add_cycles', encodeAddCyclesRequest(req), JSONParse);

return decodeAddCyclesResponse(res);
}
Expand Down Expand Up @@ -361,16 +365,25 @@ export class PocketIcClient {
);
}

private async post<B, R extends {}>(endpoint: string, body?: B): Promise<R> {
private async post<B, R extends {}>(
endpoint: string,
body?: B,
responseJsonParser?: typeof JSON.parse,
): Promise<R> {
return await this.serverClient.jsonPost<B, R>({
path: `${this.instancePath}${endpoint}`,
body,
responseJsonParser,
});
}

private async get<R extends {}>(endpoint: string): Promise<R> {
private async get<R extends {}>(
endpoint: string,
responseJsonParser?: typeof JSON.parse,
): Promise<R> {
return await this.serverClient.jsonGet<R>({
path: `${this.instancePath}${endpoint}`,
responseJsonParser,
});
}

Expand Down
26 changes: 15 additions & 11 deletions packages/pic/src/pocket-ic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import {
DeferredActor,
} from './pocket-ic-deferred-actor';

const NANOS_PER_MILLISECOND = BigInt(1_000_000);

/**
* This class represents the main PocketIC client.
* It is responsible for interacting with the PocketIC server via the REST API.
Expand Down Expand Up @@ -837,9 +839,9 @@ export class PocketIc {
* ```
*/
public async getTime(): Promise<number> {
const { millisSinceEpoch } = await this.client.getTime();
const { nanosSinceEpoch } = await this.client.getTime();

return millisSinceEpoch;
return Number(nanosSinceEpoch / NANOS_PER_MILLISECOND);
}

/**
Expand Down Expand Up @@ -1001,9 +1003,10 @@ export class PocketIc {
* ```
*/
public async advanceTime(duration: number): Promise<void> {
const currentTime = await this.getTime();
const newTime = currentTime + duration;
await this.setTime(newTime);
const { nanosSinceEpoch } = await this.client.getTime();
const durationNanos = BigInt(duration) * NANOS_PER_MILLISECOND;
const newTimeNanos = nanosSinceEpoch + durationNanos;
await this.client.setTime({ nanosSinceEpoch: newTimeNanos });
}

/**
Expand Down Expand Up @@ -1031,9 +1034,10 @@ export class PocketIc {
* ```
*/
public async advanceCertifiedTime(duration: number): Promise<void> {
const currentTime = await this.getTime();
const newTime = currentTime + duration;
await this.setCertifiedTime(newTime);
const { nanosSinceEpoch } = await this.client.getTime();
const durationNanos = BigInt(duration) * NANOS_PER_MILLISECOND;
const newTimeNanos = nanosSinceEpoch + durationNanos;
await this.client.setCertifiedTime({ nanosSinceEpoch: newTimeNanos });
}

/**
Expand Down Expand Up @@ -1222,7 +1226,7 @@ export class PocketIc {
* await picServer.stop();
* ```
*/
public async getCyclesBalance(canisterId: Principal): Promise<number> {
public async getCyclesBalance(canisterId: Principal): Promise<bigint> {
const { cycles } = await this.client.getCyclesBalance({ canisterId });

return cycles;
Expand Down Expand Up @@ -1255,8 +1259,8 @@ export class PocketIc {
*/
public async addCycles(
canisterId: Principal,
amount: number,
): Promise<number> {
amount: bigint,
): Promise<bigint> {
const { cycles } = await this.client.addCycles({ canisterId, amount });

return cycles;
Expand Down
25 changes: 25 additions & 0 deletions packages/pic/tests/src/time.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,29 @@ describe('time', () => {

jest.useRealTimers();
});

it('should advance time by 0 without setting time into the past', async () => {
const initialTime = await fixture.actor.get_time();

// This should not throw "SettingTimeIntoPast" error
await fixture.pic.advanceTime(0);
await fixture.pic.tick();

const finalTime = await fixture.actor.get_time();

// Time should not go backwards
expect(finalTime).toBeGreaterThanOrEqual(initialTime);
});

it('should advance certified time by 0 without setting time into the past', async () => {
const initialTime = await fixture.actor.get_time();

// This should not throw "SettingTimeIntoPast" error
await fixture.pic.advanceCertifiedTime(0);

const finalTime = await fixture.actor.get_time();

// Time should not go backwards
expect(finalTime).toEqual(initialTime);
});
});
Loading