Skip to content

Commit

Permalink
refactor(experimental): sysvars package: epoch schedule
Browse files Browse the repository at this point in the history
This commit introduces the `EpochSchedule` sysvar to the `@solana/sysvars` package.
  • Loading branch information
buffalojoec authored Mar 14, 2024
1 parent cc3a5ac commit ce829e2
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 2 deletions.
40 changes: 40 additions & 0 deletions packages/sysvars/src/__tests__/epoch-schedule-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 { fetchSysvarEpochSchedule, getSysvarEpochScheduleCodec } from '../epoch-schedule';
import { createLocalhostSolanaRpc } from './__setup__';

describe('epoch rewards', () => {
let rpc: Rpc<GetAccountInfoApi>;
beforeEach(() => {
rpc = createLocalhostSolanaRpc();
});
it('decode', () => {
// prettier-ignore
const epochScheduleState = new Uint8Array([
16, 39, 0, 0, 0, 0, 0, 0, // slotsPerEpoch
134, 74, 2, 0, 0, 0, 0, 0, // leaderScheduleSlotOffset
1, // warmup
38, 2, 0, 0, 0, 0, 0, 0, // firstNormalEpoch
128, 147, 220, 20, 0, 0, 0, 0, // firstNormalSlot
]);
expect(getSysvarEpochScheduleCodec().decode(epochScheduleState)).toMatchObject({
firstNormalEpoch: 550n,
firstNormalSlot: 350_000_000n,
leaderScheduleSlotOffset: 150_150n,
slotsPerEpoch: 10_000n,
warmup: true,
});
});
it('fetch', async () => {
expect.assertions(1);
const epochSchedule = await fetchSysvarEpochSchedule(rpc);
expect(epochSchedule).toMatchObject({
firstNormalEpoch: expect.any(BigInt),
firstNormalSlot: expect.any(BigInt),
leaderScheduleSlotOffset: expect.any(BigInt),
slotsPerEpoch: expect.any(BigInt),
warmup: expect.any(Boolean),
});
});
});
25 changes: 24 additions & 1 deletion packages/sysvars/src/__tests__/sysvar-test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { GetAccountInfoApi } from '@solana/rpc-api';
import type { Rpc } from '@solana/rpc-spec';

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

describe('sysvar account', () => {
Expand Down Expand Up @@ -47,4 +52,22 @@ describe('sysvar account', () => {
});
// `EpochRewards` will only appear at the start of an epoch, after epoch 0 concludes.
// See https://github.com/solana-labs/solana/blob/e0203f22dc83cb792fa97f91dbe6e924cbd08af1/docs/src/runtime/sysvars.md?plain=1#L155-L168
describe('epoch schedule', () => {
it('fetch encoded', async () => {
expect.assertions(3);
await assertValidEncodedSysvarAccount(SYSVAR_EPOCH_SCHEDULE_ADDRESS);
});
it('fetch JSON-parsed', async () => {
expect.assertions(3);
await assertValidJsonParsedSysvarAccount(SYSVAR_EPOCH_SCHEDULE_ADDRESS, {
data: {
firstNormalEpoch: expect.any(BigInt),
firstNormalSlot: expect.any(BigInt),
leaderScheduleSlotOffset: expect.any(BigInt),
slotsPerEpoch: expect.any(BigInt),
warmup: expect.any(Boolean),
},
});
});
});
});
9 changes: 9 additions & 0 deletions packages/sysvars/src/__typetests__/sysvar-typetest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { JsonParsedSysvarAccount } from '@solana/rpc-parsed-types';

import { fetchSysvarClock, type SysvarClock } from '../clock';
import { fetchSysvarEpochRewards, type SysvarEpochRewards } from '../epoch-rewards';
import { fetchSysvarEpochSchedule, type SysvarEpochSchedule } from '../epoch-schedule';
import { fetchEncodedSysvarAccount, fetchJsonParsedSysvarAccount, SYSVAR_CLOCK_ADDRESS } from '../sysvar';

const rpc = null as unknown as Parameters<typeof fetchEncodedSysvarAccount>[0];
Expand Down Expand Up @@ -61,3 +62,11 @@ const rpc = null as unknown as Parameters<typeof fetchEncodedSysvarAccount>[0];
// @ts-expect-error Returns a `SysvarEpochRewards`.
fetchSysvarEpochRewards(rpc) satisfies Promise<{ foo: string }>;
}

// `fetchSysvarEpochSchedule`
{
// Returns a `SysvarEpochSchedule`.
fetchSysvarEpochSchedule(rpc) satisfies Promise<SysvarEpochSchedule>;
// @ts-expect-error Returns a `SysvarEpochSchedule`.
fetchSysvarEpochSchedule(rpc) satisfies Promise<{ foo: string }>;
}
76 changes: 76 additions & 0 deletions packages/sysvars/src/epoch-schedule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { assertAccountExists, decodeAccount, type FetchAccountConfig } from '@solana/accounts';
import {
combineCodec,
type FixedSizeCodec,
type FixedSizeDecoder,
type FixedSizeEncoder,
getBooleanDecoder,
getBooleanEncoder,
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_EPOCH_SCHEDULE_ADDRESS } from './sysvar';

type SysvarEpochScheduleSize = 33;

/**
* The `EpochSchedule` sysvar.
*
* Information about epoch duration.
*/
export type SysvarEpochSchedule = Readonly<{
firstNormalEpoch: Epoch;
firstNormalSlot: Slot;
leaderScheduleSlotOffset: bigint;
slotsPerEpoch: bigint;
warmup: boolean;
}>;

export function getSysvarEpochScheduleEncoder(): FixedSizeEncoder<SysvarEpochSchedule, SysvarEpochScheduleSize> {
return getStructEncoder([
['slotsPerEpoch', getU64Encoder()],
['leaderScheduleSlotOffset', getU64Encoder()],
['warmup', getBooleanEncoder()],
['firstNormalEpoch', getU64Encoder()],
['firstNormalSlot', getU64Encoder()],
]) as FixedSizeEncoder<SysvarEpochSchedule, SysvarEpochScheduleSize>;
}

export function getSysvarEpochScheduleDecoder(): FixedSizeDecoder<SysvarEpochSchedule, SysvarEpochScheduleSize> {
return getStructDecoder([
['slotsPerEpoch', getU64Decoder()],
['leaderScheduleSlotOffset', getU64Decoder()],
['warmup', getBooleanDecoder()],
['firstNormalEpoch', getU64Decoder()],
['firstNormalSlot', getU64Decoder()],
]) as FixedSizeDecoder<SysvarEpochSchedule, SysvarEpochScheduleSize>;
}

export function getSysvarEpochScheduleCodec(): FixedSizeCodec<
SysvarEpochSchedule,
SysvarEpochSchedule,
SysvarEpochScheduleSize
> {
return combineCodec(getSysvarEpochScheduleEncoder(), getSysvarEpochScheduleDecoder());
}

/**
* Fetch the `EpochSchedule` sysvar.
*
* Information about epoch duration.
*/
export async function fetchSysvarEpochSchedule(
rpc: Rpc<GetAccountInfoApi>,
config?: FetchAccountConfig,
): Promise<SysvarEpochSchedule> {
const account = await fetchEncodedSysvarAccount(rpc, SYSVAR_EPOCH_SCHEDULE_ADDRESS, config);
assertAccountExists(account);
const decoded = decodeAccount(account, getSysvarEpochScheduleDecoder());
return decoded.data;
}
1 change: 1 addition & 0 deletions packages/sysvars/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './clock';
export * from './epoch-rewards';
export * from './epoch-schedule';
export * from './sysvar';
7 changes: 6 additions & 1 deletion packages/sysvars/src/sysvar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ export const SYSVAR_CLOCK_ADDRESS =
'SysvarC1ock11111111111111111111111111111111' as Address<'SysvarC1ock11111111111111111111111111111111'>;
export const SYSVAR_EPOCH_REWARDS_ADDRESS =
'SysvarEpochRewards1111111111111111111111111' as Address<'SysvarEpochRewards1111111111111111111111111'>;
export const SYSVAR_EPOCH_SCHEDULE_ADDRESS =
'SysvarEpochSchedu1e111111111111111111111111' as Address<'SysvarEpochSchedu1e111111111111111111111111'>;

type SysvarAddress = typeof SYSVAR_CLOCK_ADDRESS | typeof SYSVAR_EPOCH_REWARDS_ADDRESS;
type SysvarAddress =
| typeof SYSVAR_CLOCK_ADDRESS
| typeof SYSVAR_EPOCH_REWARDS_ADDRESS
| typeof SYSVAR_EPOCH_SCHEDULE_ADDRESS;

/**
* Fetch an encoded sysvar account.
Expand Down

0 comments on commit ce829e2

Please sign in to comment.