Skip to content

Commit

Permalink
refactor(experimental): sysvars package: clock
Browse files Browse the repository at this point in the history
This commit introduces the `Clock` sysvar to the `@solana/sysvars` package.

It also introduces the `fetchSysvar` API, for fetching sysvar accounts.
  • Loading branch information
buffalojoec committed Mar 14, 2024
1 parent 662a84f commit f08ab63
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 2 deletions.
12 changes: 12 additions & 0 deletions packages/sysvars/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@
"engine": {
"node": ">=17.4"
},
"dependencies": {
"@solana/accounts": "workspace:*",
"@solana/codecs": "workspace:*"
},
"devDependencies": {
"@solana/addresses": "workspace:*",
"@solana/rpc-api": "workspace:*",
"@solana/rpc-parsed-types": "workspace:*",
"@solana/rpc-spec": "workspace:*",
"@solana/rpc-transport-http": "workspace:*",
"@solana/rpc-types": "workspace:*"
},
"bundlewatch": {
"defaultCompression": "gzip",
"files": [
Expand Down
10 changes: 10 additions & 0 deletions packages/sysvars/src/__tests__/__setup__.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createSolanaRpcApi, type SolanaRpcApi } from '@solana/rpc-api';
import { createRpc, type Rpc } from '@solana/rpc-spec';
import { createHttpTransport } from '@solana/rpc-transport-http';

export function createLocalhostSolanaRpc(): Rpc<SolanaRpcApi> {
return createRpc({
api: createSolanaRpcApi(),
transport: createHttpTransport({ url: 'http://127.0.0.1:8899' }),
});
}
40 changes: 40 additions & 0 deletions packages/sysvars/src/__tests__/clock-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { GetAccountInfoApi } from '@solana/rpc-api';
import type { Rpc } from '@solana/rpc-spec';

import { fetchSysvarClock, getSysvarClockCodec } from '../clock';
import { createLocalhostSolanaRpc } from './__setup__';

describe('clock', () => {
let rpc: Rpc<GetAccountInfoApi>;
beforeEach(() => {
rpc = createLocalhostSolanaRpc();
});
it('decode', () => {
// prettier-ignore
const clockState = new Uint8Array([
119, 233, 246, 16, 0, 0, 0, 0, // slot
246, 255, 255, 255, 255, 255, 255, 255, // epochStartTimestamp
4, 0, 0, 0, 0, 0, 0, 0, // epoch
0, 0, 0, 0, 0, 0, 0, 0, // leaderScheduleEpoch
224, 177, 255, 255, 255, 255, 255, 255, // unixTimestamp
]);
expect(getSysvarClockCodec().decode(clockState)).toMatchObject({
epoch: 4n,
epochStartTimestamp: -10n,
leaderScheduleEpoch: 0n,
slot: 284_617_079n,
unixTimestamp: -20_000n,
});
});
it('fetch', async () => {
expect.assertions(1);
const clock = await fetchSysvarClock(rpc);
expect(clock).toMatchObject({
epoch: expect.any(BigInt),
epochStartTimestamp: expect.any(BigInt),
leaderScheduleEpoch: expect.any(BigInt),
slot: expect.any(BigInt),
unixTimestamp: expect.any(BigInt),
});
});
});
47 changes: 46 additions & 1 deletion packages/sysvars/src/__tests__/sysvar-test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,48 @@
import type { GetAccountInfoApi } from '@solana/rpc-api';
import type { Rpc } from '@solana/rpc-spec';

import { fetchEncodedSysvarAccount, fetchJsonParsedSysvarAccount, SYSVAR_CLOCK_ADDRESS } from '../sysvar';
import { createLocalhostSolanaRpc } from './__setup__';

describe('sysvar account', () => {
it.todo('tests');
let rpc: Rpc<GetAccountInfoApi>;
beforeEach(() => {
rpc = createLocalhostSolanaRpc();
});
const assertValidEncodedSysvarAccount = async (address: Parameters<typeof fetchEncodedSysvarAccount>[1]) => {
const account = await fetchEncodedSysvarAccount(rpc, address);
expect(account.address).toEqual(address);
expect(account.exists).toBe(true);
expect(account).toMatchObject({
data: expect.any(Uint8Array),
});
};
const assertValidJsonParsedSysvarAccount = async (
address: Parameters<typeof fetchEncodedSysvarAccount>[1],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any,
) => {
const account = await fetchJsonParsedSysvarAccount(rpc, address);
expect(account.address).toEqual(address);
expect(account.exists).toBe(true);
expect(account).toMatchObject(data);
};
describe('clock', () => {
it('fetch encoded', async () => {
expect.assertions(3);
await assertValidEncodedSysvarAccount(SYSVAR_CLOCK_ADDRESS);
});
it('fetch JSON-parsed', async () => {
expect.assertions(3);
await assertValidJsonParsedSysvarAccount(SYSVAR_CLOCK_ADDRESS, {
data: {
epoch: expect.any(BigInt),
epochStartTimestamp: expect.any(BigInt),
leaderScheduleEpoch: expect.any(BigInt),
slot: expect.any(BigInt),
unixTimestamp: expect.any(BigInt),
},
});
});
});
});
54 changes: 54 additions & 0 deletions packages/sysvars/src/__typetests__/sysvar-typetest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { MaybeAccount, MaybeEncodedAccount } from '@solana/accounts';
import type { Address } from '@solana/addresses';
import type { JsonParsedSysvarAccount } from '@solana/rpc-parsed-types';

import { fetchSysvarClock, type SysvarClock } from '../clock';
import { fetchEncodedSysvarAccount, fetchJsonParsedSysvarAccount, SYSVAR_CLOCK_ADDRESS } from '../sysvar';

const rpc = null as unknown as Parameters<typeof fetchEncodedSysvarAccount>[0];

// `fetchEncodedSysvarAccount`
{
// Only accepts a valid sysvar address.
fetchEncodedSysvarAccount(rpc, SYSVAR_CLOCK_ADDRESS);
// @ts-expect-error Only accepts a valid sysvar address.
fetchEncodedSysvarAccount(rpc, 'Foo1111' as Address<'Foo1111'>);

// Returns a `MaybeEncodedAccount` with the provided sysvar address.
fetchEncodedSysvarAccount(rpc, SYSVAR_CLOCK_ADDRESS) satisfies Promise<
MaybeEncodedAccount<typeof SYSVAR_CLOCK_ADDRESS>
>;
// @ts-expect-error Returns a `MaybeEncodedAccount` with the provided sysvar address.
fetchEncodedSysvarAccount(rpc, SYSVAR_CLOCK_ADDRESS) satisfies Promise<MaybeEncodedAccount<Address<'Foo1111'>>>;
}

// `fetchJsonParsedSysvarAccount`
{
// Only accepts a valid sysvar address.
fetchJsonParsedSysvarAccount(rpc, SYSVAR_CLOCK_ADDRESS);
// @ts-expect-error Only accepts a valid sysvar address.
fetchJsonParsedSysvarAccount(rpc, 'Foo1111' as Address<'Foo1111'>);

// Returns an `MaybeAccount or MaybeEncodedAccount` with the provided sysvar address,
// as well as the `JsonParsedSysvarAccount` data.
fetchJsonParsedSysvarAccount(rpc, SYSVAR_CLOCK_ADDRESS) satisfies Promise<
| MaybeAccount<JsonParsedSysvarAccount, typeof SYSVAR_CLOCK_ADDRESS>
| MaybeEncodedAccount<typeof SYSVAR_CLOCK_ADDRESS>
>;
// @ts-expect-error Returns an `MaybeAccount or MaybeEncodedAccount` with the provided address.
fetchJsonParsedSysvarAccount(rpc, SYSVAR_CLOCK_ADDRESS) satisfies Promise<
MaybeAccount<JsonParsedSysvarAccount, Address<'Foo1111'>> | MaybeEncodedAccount<Address<'Foo1111'>>
>;
// @ts-expect-error Returns an `MaybeAccount or MaybeEncodedAccount` with `JsonParsedSysvarAccount` data.
fetchJsonParsedSysvarAccount(rpc, SYSVAR_CLOCK_ADDRESS) satisfies Promise<
MaybeAccount<{ foo: string }, typeof SYSVAR_CLOCK_ADDRESS> | MaybeEncodedAccount<typeof SYSVAR_CLOCK_ADDRESS>
>;
}

// `fetchSysvarClock`
{
// Returns a `SysvarClock`.
fetchSysvarClock(rpc) satisfies Promise<SysvarClock>;
// @ts-expect-error Returns a `SysvarClock`.
fetchSysvarClock(rpc) satisfies Promise<{ foo: string }>;
}
71 changes: 71 additions & 0 deletions packages/sysvars/src/clock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { assertAccountExists, decodeAccount, type FetchAccountConfig } from '@solana/accounts';
import {
combineCodec,
type FixedSizeCodec,
type FixedSizeDecoder,
type FixedSizeEncoder,
getI64Decoder,
getI64Encoder,
getStructDecoder,
getStructEncoder,
getU64Decoder,
getU64Encoder,
} from '@solana/codecs';
import type { GetAccountInfoApi } from '@solana/rpc-api';
import type { Rpc } from '@solana/rpc-spec';
import type { Epoch, Slot } from '@solana/rpc-types';

import { fetchEncodedSysvarAccount, SYSVAR_CLOCK_ADDRESS } from './sysvar';

type UnixTimestamp = bigint;

type SysvarClockSize = 40;

/**
* The `Clock` sysvar.
*
* Information about the network’s clock, ticks, slots, etc.
*/
export type SysvarClock = Readonly<{
epoch: Epoch;
epochStartTimestamp: UnixTimestamp;
leaderScheduleEpoch: Epoch;
slot: Slot;
unixTimestamp: UnixTimestamp;
}>;

export function getSysvarClockEncoder(): FixedSizeEncoder<SysvarClock, SysvarClockSize> {
return getStructEncoder([
['slot', getU64Encoder()],
['epochStartTimestamp', getI64Encoder()],
['epoch', getU64Encoder()],
['leaderScheduleEpoch', getU64Encoder()],
['unixTimestamp', getI64Encoder()],
]) as FixedSizeEncoder<SysvarClock, SysvarClockSize>;
}

export function getSysvarClockDecoder(): FixedSizeDecoder<SysvarClock, SysvarClockSize> {
return getStructDecoder([
['slot', getU64Decoder()],
['epochStartTimestamp', getI64Decoder()],
['epoch', getU64Decoder()],
['leaderScheduleEpoch', getU64Decoder()],
['unixTimestamp', getI64Decoder()],
]) as FixedSizeDecoder<SysvarClock, SysvarClockSize>;
}

export function getSysvarClockCodec(): FixedSizeCodec<SysvarClock, SysvarClock, SysvarClockSize> {
return combineCodec(getSysvarClockEncoder(), getSysvarClockDecoder());
}

/**
* Fetch the `Clock` sysvar.
*
* Information about the network’s clock, ticks, slots, etc.
*/
export async function fetchSysvarClock(rpc: Rpc<GetAccountInfoApi>, config?: FetchAccountConfig): Promise<SysvarClock> {
const account = await fetchEncodedSysvarAccount(rpc, SYSVAR_CLOCK_ADDRESS, config);
assertAccountExists(account);
const decoded = decodeAccount(account, getSysvarClockDecoder());
return decoded.data;
}
2 changes: 2 additions & 0 deletions packages/sysvars/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './clock';
export * from './sysvar';
44 changes: 44 additions & 0 deletions packages/sysvars/src/sysvar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
type FetchAccountConfig,
fetchEncodedAccount,
fetchJsonParsedAccount,
type MaybeAccount,
type MaybeEncodedAccount,
} from '@solana/accounts';
import type { Address } from '@solana/addresses';
import type { GetAccountInfoApi } from '@solana/rpc-api';
import type { JsonParsedSysvarAccount } from '@solana/rpc-parsed-types';
import type { Rpc } from '@solana/rpc-spec';

export const SYSVAR_CLOCK_ADDRESS =
'SysvarC1ock11111111111111111111111111111111' as Address<'SysvarC1ock11111111111111111111111111111111'>;

type SysvarAddress = typeof SYSVAR_CLOCK_ADDRESS;

/**
* Fetch an encoded sysvar account.
*
* Sysvars are special accounts that contain dynamically-updated data about the
* network cluster, the blockchain history, and the executing transaction.
*/
export async function fetchEncodedSysvarAccount<TAddress extends SysvarAddress>(
rpc: Rpc<GetAccountInfoApi>,
address: TAddress,
config?: FetchAccountConfig,
): Promise<MaybeEncodedAccount<TAddress>> {
return fetchEncodedAccount<TAddress>(rpc, address, config);
}

/**
* Fetch a JSON-parsed sysvar account.
*
* Sysvars are special accounts that contain dynamically-updated data about the
* network cluster, the blockchain history, and the executing transaction.
*/
export async function fetchJsonParsedSysvarAccount<TAddress extends SysvarAddress>(
rpc: Rpc<GetAccountInfoApi>,
address: TAddress,
config?: FetchAccountConfig,
): Promise<MaybeAccount<JsonParsedSysvarAccount, TAddress> | MaybeEncodedAccount<TAddress>> {
return fetchJsonParsedAccount<JsonParsedSysvarAccount, TAddress>(rpc, address, config);
}
28 changes: 27 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f08ab63

Please sign in to comment.