| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- import {
- accountValueNode,
- argumentValueNode,
- constantDiscriminatorNode,
- constantPdaSeedNodeFromString,
- constantValueNodeFromBytes,
- fieldDiscriminatorNode,
- instructionAccountNode,
- instructionArgumentNode,
- instructionNode,
- numberTypeNode,
- numberValueNode,
- pdaNode,
- pdaSeedValueNode,
- pdaValueNode,
- programNode,
- publicKeyTypeNode,
- resolverValueNode,
- variablePdaSeedNode,
- } from '@kinobi-so/nodes';
- import { visit } from '@kinobi-so/visitors-core';
- import { test } from 'vitest';
- import { getRenderMapVisitor } from '../src';
- import { codeContains, codeDoesNotContain, renderMapContains, renderMapContainsImports } from './_setup';
- test('it renders instruction accounts that can either be signer or non-signer', async () => {
- // Given the following instruction with a signer or non-signer account.
- const node = programNode({
- instructions: [
- instructionNode({
- accounts: [instructionAccountNode({ isSigner: 'either', isWritable: false, name: 'myAccount' })],
- name: 'myInstruction',
- }),
- ],
- name: 'myProgram',
- publicKey: '1111',
- });
- // When we render it.
- const renderMap = visit(node, getRenderMapVisitor());
- // Then we expect the input to be rendered as either a signer or non-signer.
- await renderMapContains(renderMap, 'instructions/myInstruction.ts', [
- 'myAccount: Address<TAccountMyAccount> | TransactionSigner<TAccountMyAccount>;',
- ]);
- });
- test('it renders extra arguments that default on each other', async () => {
- // Given the following instruction with two extra arguments
- // such that one defaults to the other.
- const node = programNode({
- instructions: [
- instructionNode({
- extraArguments: [
- instructionArgumentNode({
- defaultValue: argumentValueNode('bar'),
- name: 'foo',
- type: numberTypeNode('u64'),
- }),
- instructionArgumentNode({
- name: 'bar',
- type: numberTypeNode('u64'),
- }),
- ],
- name: 'create',
- }),
- ],
- name: 'myProgram',
- publicKey: '1111',
- });
- // When we render it.
- const renderMap = visit(node, getRenderMapVisitor());
- // Then we expect the following code to be rendered.
- await renderMapContains(renderMap, 'instructions/create.ts', [
- 'const args = { ...input }',
- 'if (!args.foo) { args.foo = expectSome(args.bar); }',
- ]);
- });
- test('it renders the args variable on the async function only if the extra argument has an async default value', async () => {
- // Given the following instruction with an async resolver and an extra argument.
- const node = programNode({
- instructions: [
- instructionNode({
- extraArguments: [
- instructionArgumentNode({
- defaultValue: resolverValueNode('myAsyncResolver'),
- name: 'foo',
- type: numberTypeNode('u64'),
- }),
- ],
- name: 'create',
- }),
- ],
- name: 'myProgram',
- publicKey: '1111',
- });
- // When we render it.
- const renderMap = visit(node, getRenderMapVisitor({ asyncResolvers: ['myAsyncResolver'] }));
- // And split the async and sync functions.
- const [asyncFunction, syncFunction] = renderMap
- .get('instructions/create.ts')
- .split(/export\s+function\s+getCreateInstruction/);
- // Then we expect only the async function to contain the args variable.
- await codeContains(asyncFunction, ['// Original args.', 'const args = { ...input }']);
- await codeDoesNotContain(syncFunction, ['// Original args.', 'const args = { ...input }']);
- });
- test('it only renders the args variable on the async function if the extra argument is used in an async default value', async () => {
- // Given the following instruction with an async resolver depending on
- // an extra argument such that the instruction has no data arguments.
- const node = programNode({
- instructions: [
- instructionNode({
- accounts: [
- instructionAccountNode({
- defaultValue: resolverValueNode('myAsyncResolver', { dependsOn: [argumentValueNode('bar')] }),
- isSigner: false,
- isWritable: false,
- name: 'foo',
- }),
- ],
- extraArguments: [
- instructionArgumentNode({
- name: 'bar',
- type: numberTypeNode('u64'),
- }),
- ],
- name: 'create',
- }),
- ],
- name: 'myProgram',
- publicKey: '1111',
- });
- // When we render it.
- const renderMap = visit(node, getRenderMapVisitor({ asyncResolvers: ['myAsyncResolver'] }));
- // And split the async and sync functions.
- const [asyncFunction, syncFunction] = renderMap
- .get('instructions/create.ts')
- .split(/export\s+function\s+getCreateInstruction/);
- // Then we expect only the async function to contain the args variable.
- await codeContains(asyncFunction, ['// Original args.', 'const args = { ...input }']);
- await codeDoesNotContain(syncFunction, ['// Original args.', 'const args = { ...input }']);
- });
- test('it renders instruction accounts with linked PDAs as default value', async () => {
- // Given the following program with a PDA node and an instruction account using it as default value.
- const node = programNode({
- instructions: [
- instructionNode({
- accounts: [
- instructionAccountNode({ isSigner: true, isWritable: false, name: 'authority' }),
- instructionAccountNode({
- defaultValue: pdaValueNode('counter', [
- pdaSeedValueNode('authority', accountValueNode('authority')),
- ]),
- isSigner: false,
- isWritable: false,
- name: 'counter',
- }),
- ],
- name: 'increment',
- }),
- ],
- name: 'counter',
- pdas: [
- pdaNode({
- name: 'counter',
- seeds: [
- constantPdaSeedNodeFromString('utf8', 'counter'),
- variablePdaSeedNode('authority', publicKeyTypeNode()),
- ],
- }),
- ],
- publicKey: '1111',
- });
- // When we render it.
- const renderMap = visit(node, getRenderMapVisitor());
- // Then we expect the following default value to be rendered.
- await renderMapContains(renderMap, 'instructions/increment.ts', [
- 'if (!accounts.counter.value) { ' +
- 'accounts.counter.value = await findCounterPda( { authority: expectAddress ( accounts.authority.value ) } ); ' +
- '}',
- ]);
- renderMapContainsImports(renderMap, 'instructions/increment.ts', { '../pdas': ['findCounterPda'] });
- });
- test('it renders instruction accounts with inlined PDAs as default value', async () => {
- // Given the following instruction with an inlined PDA default value.
- const node = programNode({
- instructions: [
- instructionNode({
- accounts: [
- instructionAccountNode({ isSigner: true, isWritable: false, name: 'authority' }),
- instructionAccountNode({
- defaultValue: pdaValueNode(
- pdaNode({
- name: 'counter',
- seeds: [
- constantPdaSeedNodeFromString('utf8', 'counter'),
- variablePdaSeedNode('authority', publicKeyTypeNode()),
- ],
- }),
- [pdaSeedValueNode('authority', accountValueNode('authority'))],
- ),
- isSigner: false,
- isWritable: false,
- name: 'counter',
- }),
- ],
- name: 'increment',
- }),
- ],
- name: 'counter',
- publicKey: '1111',
- });
- // When we render it.
- const renderMap = visit(node, getRenderMapVisitor());
- // Then we expect the following default value to be rendered.
- await renderMapContains(renderMap, 'instructions/increment.ts', [
- 'if (!accounts.counter.value) { ' +
- 'accounts.counter.value = await getProgramDerivedAddress( { ' +
- ' programAddress, ' +
- ' seeds: [ ' +
- " getUtf8Encoder().encode('counter'), " +
- ' getAddressEncoder().encode(expectAddress(accounts.authority.value)) ' +
- ' ] ' +
- '} ); ' +
- '}',
- ]);
- renderMapContainsImports(renderMap, 'instructions/increment.ts', {
- '@solana/web3.js': ['getProgramDerivedAddress'],
- });
- });
- test('it renders instruction accounts with inlined PDAs from another program as default value', async () => {
- // Given the following instruction with an inlined PDA default value from another program.
- const node = programNode({
- instructions: [
- instructionNode({
- accounts: [
- instructionAccountNode({ isSigner: true, isWritable: false, name: 'authority' }),
- instructionAccountNode({
- defaultValue: pdaValueNode(
- pdaNode({
- name: 'counter',
- programId: '2222',
- seeds: [
- constantPdaSeedNodeFromString('utf8', 'counter'),
- variablePdaSeedNode('authority', publicKeyTypeNode()),
- ],
- }),
- [pdaSeedValueNode('authority', accountValueNode('authority'))],
- ),
- isSigner: false,
- isWritable: false,
- name: 'counter',
- }),
- ],
- name: 'increment',
- }),
- ],
- name: 'counter',
- publicKey: '1111',
- });
- // When we render it.
- const renderMap = visit(node, getRenderMapVisitor());
- // Then we expect the following default value to be rendered.
- await renderMapContains(renderMap, 'instructions/increment.ts', [
- 'if (!accounts.counter.value) { ' +
- 'accounts.counter.value = await getProgramDerivedAddress( { ' +
- " programAddress: '2222' as Address<'2222'>, " +
- ' seeds: [ ' +
- " getUtf8Encoder().encode('counter'), " +
- ' getAddressEncoder().encode(expectAddress(accounts.authority.value)) ' +
- ' ] ' +
- '} ); ' +
- '}',
- ]);
- renderMapContainsImports(renderMap, 'instructions/increment.ts', {
- '@solana/web3.js': ['Address', 'getProgramDerivedAddress'],
- });
- });
- test('it renders constants for instruction field discriminators', async () => {
- // Given the following instruction with a field discriminator.
- const node = programNode({
- instructions: [
- instructionNode({
- arguments: [
- instructionArgumentNode({
- defaultValue: numberValueNode(42),
- defaultValueStrategy: 'omitted',
- name: 'myDiscriminator',
- type: numberTypeNode('u64'),
- }),
- ],
- discriminators: [fieldDiscriminatorNode('myDiscriminator')],
- name: 'myInstruction',
- }),
- ],
- name: 'myProgram',
- publicKey: '1111',
- });
- // When we render it.
- const renderMap = visit(node, getRenderMapVisitor());
- // Then we expect the following constant and function to be rendered
- // And we expect the field default value to use that constant.
- await renderMapContains(renderMap, 'instructions/myInstruction.ts', [
- 'export const MY_INSTRUCTION_MY_DISCRIMINATOR = 42;',
- 'export function getMyInstructionMyDiscriminatorBytes() { return getU64Encoder().encode(MY_INSTRUCTION_MY_DISCRIMINATOR); }',
- '(value) => ({ ...value, myDiscriminator: MY_INSTRUCTION_MY_DISCRIMINATOR })',
- ]);
- });
- test('it renders constants for instruction constant discriminators', async () => {
- // Given the following instruction with two constant discriminators.
- const node = programNode({
- instructions: [
- instructionNode({
- discriminators: [
- constantDiscriminatorNode(constantValueNodeFromBytes('base16', '1111')),
- constantDiscriminatorNode(constantValueNodeFromBytes('base16', '2222'), 2),
- ],
- name: 'myInstruction',
- }),
- ],
- name: 'myProgram',
- publicKey: '1111',
- });
- // When we render it.
- const renderMap = visit(node, getRenderMapVisitor());
- // Then we expect the following constants and functions to be rendered.
- await renderMapContains(renderMap, 'instructions/myInstruction.ts', [
- 'export const MY_INSTRUCTION_DISCRIMINATOR = new Uint8Array([ 17, 17 ]);',
- 'export function getMyInstructionDiscriminatorBytes() { return getBytesEncoder().encode(MY_INSTRUCTION_DISCRIMINATOR); }',
- 'export const MY_INSTRUCTION_DISCRIMINATOR2 = new Uint8Array([ 34, 34 ]);',
- 'export function getMyInstructionDiscriminator2Bytes() { return getBytesEncoder().encode(MY_INSTRUCTION_DISCRIMINATOR2); }',
- ]);
- });
|