From c5dfd7a2d7b2c935f20e08cf512546ac6631eb06 Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Tue, 18 Nov 2025 11:35:26 +0000 Subject: [PATCH] Unwrap generics from Anchor --- .changeset/four-bears-shave.md | 6 + packages/errors/src/codes.ts | 2 + packages/errors/src/context.ts | 4 + packages/errors/src/messages.ts | 2 + .../nodes-from-anchor/src/discriminators.ts | 2 +- .../nodes-from-anchor/src/v01/AccountNode.ts | 17 +- .../src/v01/DefinedTypeNode.ts | 7 +- .../src/v01/InstructionArgumentNode.ts | 7 +- .../src/v01/InstructionNode.ts | 11 +- .../nodes-from-anchor/src/v01/ProgramNode.ts | 14 +- packages/nodes-from-anchor/src/v01/index.ts | 3 +- .../src/v01/typeNodes/ArrayTypeNode.ts | 13 +- .../typeNodes/EnumStructVariantTypeNode.ts | 6 +- .../v01/typeNodes/EnumTupleVariantTypeNode.ts | 6 +- .../src/v01/typeNodes/EnumTypeNode.ts | 12 +- .../src/v01/typeNodes/OptionTypeNode.ts | 10 +- .../src/v01/typeNodes/StructFieldTypeNode.ts | 10 +- .../src/v01/typeNodes/StructTypeNode.ts | 7 +- .../src/v01/typeNodes/TupleTypeNode.ts | 7 +- .../src/v01/typeNodes/TypeNode.ts | 31 ++-- .../src/v01/unwrapGenerics.ts | 63 +++++++ .../test/v01/AccountNode.test.ts | 5 +- .../test/v01/DefinedTypeNode.test.ts | 93 +++++++++- .../test/v01/InstructionArgumentNode.test.ts | 15 +- .../test/v01/InstructionNode.test.ts | 21 ++- .../test/v01/ProgramNode.test.ts | 161 ++++++++++++++++++ .../test/v01/typeNodes/ArrayTypeNode.test.ts | 23 ++- .../v01/typeNodes/BooleanTypeNode.test.ts | 6 +- .../test/v01/typeNodes/BytesTypeNode.test.ts | 8 +- .../test/v01/typeNodes/EnumTypeNode.test.ts | 23 ++- .../test/v01/typeNodes/NumberTypeNode.test.ts | 30 ++-- .../test/v01/typeNodes/OptionTypeNode.test.ts | 10 +- .../v01/typeNodes/PublicKeyTypeNode.test.ts | 6 +- .../test/v01/typeNodes/StringTypeNode.test.ts | 8 +- .../test/v01/typeNodes/StructTypeNode.test.ts | 34 ++-- .../test/v01/typeNodes/TupleTypeNode.test.ts | 15 +- 36 files changed, 560 insertions(+), 138 deletions(-) create mode 100644 .changeset/four-bears-shave.md create mode 100644 packages/nodes-from-anchor/src/v01/unwrapGenerics.ts diff --git a/.changeset/four-bears-shave.md b/.changeset/four-bears-shave.md new file mode 100644 index 000000000..81ca77b87 --- /dev/null +++ b/.changeset/four-bears-shave.md @@ -0,0 +1,6 @@ +--- +'@codama/nodes-from-anchor': patch +'@codama/errors': patch +--- + +Unwrap generic types and constants from Anchor IDLs when converting them to Codama IDLs. diff --git a/packages/errors/src/codes.ts b/packages/errors/src/codes.ts index fd783e4d8..8740250b0 100644 --- a/packages/errors/src/codes.ts +++ b/packages/errors/src/codes.ts @@ -61,6 +61,7 @@ export const CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING = 2100002; export const CODAMA_ERROR__ANCHOR__TYPE_PATH_MISSING = 2100003; export const CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED = 2100004; export const CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED = 2100005; +export const CODAMA_ERROR__ANCHOR__GENERIC_TYPE_MISSING = 2100006; // Renderers-related errors. // Reserve error codes in the range [2800000-2800999]. @@ -84,6 +85,7 @@ export const CODAMA_ERROR__RENDERERS__UNSUPPORTED_NODE = 2800000; export type CodamaErrorCode = | typeof CODAMA_ERROR__ANCHOR__ACCOUNT_TYPE_MISSING | typeof CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING + | typeof CODAMA_ERROR__ANCHOR__GENERIC_TYPE_MISSING | typeof CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED | typeof CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED | typeof CODAMA_ERROR__ANCHOR__TYPE_PATH_MISSING diff --git a/packages/errors/src/context.ts b/packages/errors/src/context.ts index 108c38c03..1663a20df 100644 --- a/packages/errors/src/context.ts +++ b/packages/errors/src/context.ts @@ -22,6 +22,7 @@ import { import { CODAMA_ERROR__ANCHOR__ACCOUNT_TYPE_MISSING, CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING, + CODAMA_ERROR__ANCHOR__GENERIC_TYPE_MISSING, CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED, CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED, CODAMA_ERROR__ANCHOR__TYPE_PATH_MISSING, @@ -69,6 +70,9 @@ export type CodamaErrorContext = DefaultUnspecifiedErrorContextToUndefined<{ [CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING]: { name: string; }; + [CODAMA_ERROR__ANCHOR__GENERIC_TYPE_MISSING]: { + name: string; + }; [CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED]: { kind: string; }; diff --git a/packages/errors/src/messages.ts b/packages/errors/src/messages.ts index 2efa4e5da..d255934de 100644 --- a/packages/errors/src/messages.ts +++ b/packages/errors/src/messages.ts @@ -6,6 +6,7 @@ import { CODAMA_ERROR__ANCHOR__ACCOUNT_TYPE_MISSING, CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING, + CODAMA_ERROR__ANCHOR__GENERIC_TYPE_MISSING, CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED, CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED, CODAMA_ERROR__ANCHOR__TYPE_PATH_MISSING, @@ -49,6 +50,7 @@ export const CodamaErrorMessages: Readonly<{ }> = { [CODAMA_ERROR__ANCHOR__ACCOUNT_TYPE_MISSING]: 'Account type [$name] is missing from the IDL types.', [CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING]: 'Argument name [$name] is missing from the instruction definition.', + [CODAMA_ERROR__ANCHOR__GENERIC_TYPE_MISSING]: 'Generic type [$name] is missing from the IDL types.', [CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED]: 'Program ID kind [$kind] is not implemented.', [CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED]: 'Seed kind [$kind] is not implemented.', [CODAMA_ERROR__ANCHOR__TYPE_PATH_MISSING]: 'Field type is missing for path [$path] in [$idlType].', diff --git a/packages/nodes-from-anchor/src/discriminators.ts b/packages/nodes-from-anchor/src/discriminators.ts index 959e9cf42..a3a682e43 100644 --- a/packages/nodes-from-anchor/src/discriminators.ts +++ b/packages/nodes-from-anchor/src/discriminators.ts @@ -1,5 +1,5 @@ import { BytesValueNode, bytesValueNode, pascalCase, snakeCase } from '@codama/nodes'; -import { sha256 } from '@noble/hashes/sha256'; +import { sha256 } from '@noble/hashes/sha2'; import { hex } from './utils'; diff --git a/packages/nodes-from-anchor/src/v01/AccountNode.ts b/packages/nodes-from-anchor/src/v01/AccountNode.ts index 5ed30d950..f341850ef 100644 --- a/packages/nodes-from-anchor/src/v01/AccountNode.ts +++ b/packages/nodes-from-anchor/src/v01/AccountNode.ts @@ -12,10 +12,15 @@ import { } from '@codama/nodes'; import { getAnchorDiscriminatorV01 } from './../discriminators'; -import { IdlV01Account, IdlV01TypeDef } from './idl'; +import type { IdlV01Account, IdlV01TypeDef } from './idl'; import { typeNodeFromAnchorV01 } from './typeNodes'; +import type { GenericsV01 } from './unwrapGenerics'; -export function accountNodeFromAnchorV01(idl: IdlV01Account, types: IdlV01TypeDef[]): AccountNode { +export function accountNodeFromAnchorV01( + idl: IdlV01Account, + types: IdlV01TypeDef[], + generics: GenericsV01, +): AccountNode { const name = camelCase(idl.name); const type = types.find(({ name }) => name === idl.name); @@ -23,7 +28,7 @@ export function accountNodeFromAnchorV01(idl: IdlV01Account, types: IdlV01TypeDe throw new CodamaError(CODAMA_ERROR__ANCHOR__ACCOUNT_TYPE_MISSING, { name: idl.name }); } - const data = typeNodeFromAnchorV01(type.type); + const data = typeNodeFromAnchorV01(type.type, generics); assertIsNode(data, 'structTypeNode'); const discriminator = structFieldTypeNode({ @@ -39,9 +44,3 @@ export function accountNodeFromAnchorV01(idl: IdlV01Account, types: IdlV01TypeDe name, }); } - -export function accountNodeFromAnchorV01WithTypeDefinition(types: IdlV01TypeDef[]) { - return function (idl: IdlV01Account): AccountNode { - return accountNodeFromAnchorV01(idl, types); - }; -} diff --git a/packages/nodes-from-anchor/src/v01/DefinedTypeNode.ts b/packages/nodes-from-anchor/src/v01/DefinedTypeNode.ts index 71e3224bd..516fb75db 100644 --- a/packages/nodes-from-anchor/src/v01/DefinedTypeNode.ts +++ b/packages/nodes-from-anchor/src/v01/DefinedTypeNode.ts @@ -1,11 +1,12 @@ import { DefinedTypeNode, definedTypeNode } from '@codama/nodes'; -import { IdlV01TypeDef } from './idl'; +import type { IdlV01TypeDef } from './idl'; import { typeNodeFromAnchorV01 } from './typeNodes'; +import type { GenericsV01 } from './unwrapGenerics'; -export function definedTypeNodeFromAnchorV01(idl: Partial): DefinedTypeNode { +export function definedTypeNodeFromAnchorV01(idl: Partial, generics: GenericsV01): DefinedTypeNode { const name = idl.name ?? ''; const idlType = idl.type ?? { fields: [], kind: 'struct' }; - const type = typeNodeFromAnchorV01(idlType); + const type = typeNodeFromAnchorV01(idlType, generics); return definedTypeNode({ docs: idl.docs, name, type }); } diff --git a/packages/nodes-from-anchor/src/v01/InstructionArgumentNode.ts b/packages/nodes-from-anchor/src/v01/InstructionArgumentNode.ts index 834338aab..7142b9f0f 100644 --- a/packages/nodes-from-anchor/src/v01/InstructionArgumentNode.ts +++ b/packages/nodes-from-anchor/src/v01/InstructionArgumentNode.ts @@ -1,12 +1,13 @@ import { InstructionArgumentNode, instructionArgumentNode } from '@codama/nodes'; -import { IdlV01Field } from './idl'; +import type { IdlV01Field } from './idl'; import { typeNodeFromAnchorV01 } from './typeNodes'; +import type { GenericsV01 } from './unwrapGenerics'; -export function instructionArgumentNodeFromAnchorV01(idl: IdlV01Field): InstructionArgumentNode { +export function instructionArgumentNodeFromAnchorV01(idl: IdlV01Field, generics: GenericsV01): InstructionArgumentNode { return instructionArgumentNode({ docs: idl.docs ?? [], name: idl.name, - type: typeNodeFromAnchorV01(idl.type), + type: typeNodeFromAnchorV01(idl.type, generics), }); } diff --git a/packages/nodes-from-anchor/src/v01/InstructionNode.ts b/packages/nodes-from-anchor/src/v01/InstructionNode.ts index 67b9b7e00..2b14df22b 100644 --- a/packages/nodes-from-anchor/src/v01/InstructionNode.ts +++ b/packages/nodes-from-anchor/src/v01/InstructionNode.ts @@ -10,13 +10,18 @@ import { } from '@codama/nodes'; import { getAnchorDiscriminatorV01 } from '../discriminators'; -import { IdlV01Instruction } from './idl'; +import type { IdlV01Instruction } from './idl'; import { instructionAccountNodesFromAnchorV01 } from './InstructionAccountNode'; import { instructionArgumentNodeFromAnchorV01 } from './InstructionArgumentNode'; +import type { GenericsV01 } from './unwrapGenerics'; -export function instructionNodeFromAnchorV01(allAccounts: AccountNode[], idl: IdlV01Instruction): InstructionNode { +export function instructionNodeFromAnchorV01( + allAccounts: AccountNode[], + idl: IdlV01Instruction, + generics: GenericsV01, +): InstructionNode { const name = idl.name; - let dataArguments = idl.args.map(instructionArgumentNodeFromAnchorV01); + let dataArguments = idl.args.map(arg => instructionArgumentNodeFromAnchorV01(arg, generics)); const discriminatorField = instructionArgumentNode({ defaultValue: getAnchorDiscriminatorV01(idl.discriminator), diff --git a/packages/nodes-from-anchor/src/v01/ProgramNode.ts b/packages/nodes-from-anchor/src/v01/ProgramNode.ts index 6f6c678f7..5430a6fc9 100644 --- a/packages/nodes-from-anchor/src/v01/ProgramNode.ts +++ b/packages/nodes-from-anchor/src/v01/ProgramNode.ts @@ -1,27 +1,29 @@ import { ProgramNode, programNode, ProgramVersion } from '@codama/nodes'; -import { accountNodeFromAnchorV01WithTypeDefinition } from './AccountNode'; +import { accountNodeFromAnchorV01 } from './AccountNode'; import { definedTypeNodeFromAnchorV01 } from './DefinedTypeNode'; import { errorNodeFromAnchorV01 } from './ErrorNode'; import { IdlV01 } from './idl'; import { instructionNodeFromAnchorV01 } from './InstructionNode'; +import { extractGenerics } from './unwrapGenerics'; export function programNodeFromAnchorV01(idl: IdlV01): ProgramNode { - const types = idl.types ?? []; + const [types, generics] = extractGenerics(idl.types ?? []); const accounts = idl.accounts ?? []; const instructions = idl.instructions ?? []; const errors = idl.errors ?? []; const filteredTypes = types.filter(type => !accounts.some(account => account.name === type.name)); - const definedTypes = filteredTypes.map(definedTypeNodeFromAnchorV01); - const accountNodeFromAnchorV01 = accountNodeFromAnchorV01WithTypeDefinition(types); - const accountNodes = accounts.map(accountNodeFromAnchorV01); + const definedTypes = filteredTypes.map(type => definedTypeNodeFromAnchorV01(type, generics)); + const accountNodes = accounts.map(account => accountNodeFromAnchorV01(account, types, generics)); return programNode({ accounts: accountNodes, definedTypes, errors: errors.map(errorNodeFromAnchorV01), - instructions: instructions.map(instruction => instructionNodeFromAnchorV01(accountNodes, instruction)), + instructions: instructions.map(instruction => + instructionNodeFromAnchorV01(accountNodes, instruction, generics), + ), name: idl.metadata.name, origin: 'anchor', publicKey: idl.address, diff --git a/packages/nodes-from-anchor/src/v01/index.ts b/packages/nodes-from-anchor/src/v01/index.ts index 1a2f32ef3..87e38349a 100644 --- a/packages/nodes-from-anchor/src/v01/index.ts +++ b/packages/nodes-from-anchor/src/v01/index.ts @@ -1,10 +1,11 @@ export * from './AccountNode'; export * from './DefinedTypeNode'; export * from './ErrorNode'; +export * from './idl'; export * from './InstructionAccountNode'; export * from './InstructionArgumentNode'; export * from './InstructionNode'; export * from './ProgramNode'; export * from './RootNode'; -export * from './idl'; export * from './typeNodes'; +export * from './unwrapGenerics'; diff --git a/packages/nodes-from-anchor/src/v01/typeNodes/ArrayTypeNode.ts b/packages/nodes-from-anchor/src/v01/typeNodes/ArrayTypeNode.ts index be661f54a..000416f89 100644 --- a/packages/nodes-from-anchor/src/v01/typeNodes/ArrayTypeNode.ts +++ b/packages/nodes-from-anchor/src/v01/typeNodes/ArrayTypeNode.ts @@ -1,15 +1,18 @@ import { ArrayTypeNode, arrayTypeNode, fixedCountNode, numberTypeNode, prefixedCountNode } from '@codama/nodes'; -import { IdlV01TypeArray, IdlV01TypeVec } from '../idl'; +import type { IdlV01TypeArray, IdlV01TypeVec } from '../idl'; +import type { GenericsV01 } from '../unwrapGenerics'; import { typeNodeFromAnchorV01 } from './TypeNode'; -export function arrayTypeNodeFromAnchorV01(idl: IdlV01TypeArray | IdlV01TypeVec): ArrayTypeNode { +export function arrayTypeNodeFromAnchorV01(idl: IdlV01TypeArray | IdlV01TypeVec, generics: GenericsV01): ArrayTypeNode { if ('array' in idl) { - const item = typeNodeFromAnchorV01(idl.array[0]); - return arrayTypeNode(item, fixedCountNode(idl.array[1] as number)); + const item = typeNodeFromAnchorV01(idl.array[0], generics); + const size = + typeof idl.array[1] === 'number' ? idl.array[1] : parseInt(generics.constArgs[idl.array[1].generic].value); + return arrayTypeNode(item, fixedCountNode(size)); } - const item = typeNodeFromAnchorV01(idl.vec); + const item = typeNodeFromAnchorV01(idl.vec, generics); return arrayTypeNode(item, prefixedCountNode(numberTypeNode('u32'))); } diff --git a/packages/nodes-from-anchor/src/v01/typeNodes/EnumStructVariantTypeNode.ts b/packages/nodes-from-anchor/src/v01/typeNodes/EnumStructVariantTypeNode.ts index 22bd11b7c..1cd53be41 100644 --- a/packages/nodes-from-anchor/src/v01/typeNodes/EnumStructVariantTypeNode.ts +++ b/packages/nodes-from-anchor/src/v01/typeNodes/EnumStructVariantTypeNode.ts @@ -1,13 +1,15 @@ import { EnumStructVariantTypeNode, enumStructVariantTypeNode, StructTypeNode } from '@codama/nodes'; -import { IdlV01DefinedFieldsNamed, IdlV01EnumVariant } from '../idl'; +import type { IdlV01DefinedFieldsNamed, IdlV01EnumVariant } from '../idl'; +import type { GenericsV01 } from '../unwrapGenerics'; import { structTypeNodeFromAnchorV01 } from './StructTypeNode'; export function enumStructVariantTypeNodeFromAnchorV01( idl: IdlV01EnumVariant & { fields: IdlV01DefinedFieldsNamed }, + generics: GenericsV01, ): EnumStructVariantTypeNode { return enumStructVariantTypeNode( idl.name ?? '', - structTypeNodeFromAnchorV01({ fields: idl.fields, kind: 'struct' }), + structTypeNodeFromAnchorV01({ fields: idl.fields, kind: 'struct' }, generics), ); } diff --git a/packages/nodes-from-anchor/src/v01/typeNodes/EnumTupleVariantTypeNode.ts b/packages/nodes-from-anchor/src/v01/typeNodes/EnumTupleVariantTypeNode.ts index 7799292d8..1f6f3938b 100644 --- a/packages/nodes-from-anchor/src/v01/typeNodes/EnumTupleVariantTypeNode.ts +++ b/packages/nodes-from-anchor/src/v01/typeNodes/EnumTupleVariantTypeNode.ts @@ -1,10 +1,12 @@ import { EnumTupleVariantTypeNode, enumTupleVariantTypeNode, TupleTypeNode } from '@codama/nodes'; -import { IdlV01DefinedFieldsTuple, IdlV01EnumVariant } from '../idl'; +import type { IdlV01DefinedFieldsTuple, IdlV01EnumVariant } from '../idl'; +import type { GenericsV01 } from '../unwrapGenerics'; import { tupleTypeNodeFromAnchorV01 } from './TupleTypeNode'; export function enumTupleVariantTypeNodeFromAnchorV01( idl: IdlV01EnumVariant & { fields: IdlV01DefinedFieldsTuple }, + generics: GenericsV01, ): EnumTupleVariantTypeNode { - return enumTupleVariantTypeNode(idl.name ?? '', tupleTypeNodeFromAnchorV01(idl.fields)); + return enumTupleVariantTypeNode(idl.name ?? '', tupleTypeNodeFromAnchorV01(idl.fields, generics)); } diff --git a/packages/nodes-from-anchor/src/v01/typeNodes/EnumTypeNode.ts b/packages/nodes-from-anchor/src/v01/typeNodes/EnumTypeNode.ts index 82c18e42b..255bdbf46 100644 --- a/packages/nodes-from-anchor/src/v01/typeNodes/EnumTypeNode.ts +++ b/packages/nodes-from-anchor/src/v01/typeNodes/EnumTypeNode.ts @@ -1,22 +1,30 @@ import { EnumTypeNode, enumTypeNode, EnumVariantTypeNode, NumberTypeNode } from '@codama/nodes'; -import { IdlV01DefinedFieldsNamed, IdlV01DefinedFieldsTuple, IdlV01EnumVariant, IdlV01TypeDefTyEnum } from '../idl'; +import type { + IdlV01DefinedFieldsNamed, + IdlV01DefinedFieldsTuple, + IdlV01EnumVariant, + IdlV01TypeDefTyEnum, +} from '../idl'; +import type { GenericsV01 } from '../unwrapGenerics'; import { enumEmptyVariantTypeNodeFromAnchorV01 } from './EnumEmptyVariantTypeNode'; import { enumStructVariantTypeNodeFromAnchorV01 } from './EnumStructVariantTypeNode'; import { enumTupleVariantTypeNodeFromAnchorV01 } from './EnumTupleVariantTypeNode'; export function enumTypeNodeFromAnchorV01( idl: IdlV01TypeDefTyEnum, + generics: GenericsV01, ): EnumTypeNode { const variants = idl.variants.map((variant): EnumVariantTypeNode => { if (!variant.fields || variant.fields.length <= 0) { return enumEmptyVariantTypeNodeFromAnchorV01(variant); } if (isStructVariant(variant)) { - return enumStructVariantTypeNodeFromAnchorV01(variant); + return enumStructVariantTypeNodeFromAnchorV01(variant, generics); } return enumTupleVariantTypeNodeFromAnchorV01( variant as IdlV01EnumVariant & { fields: IdlV01DefinedFieldsTuple }, + generics, ); }); return enumTypeNode(variants); diff --git a/packages/nodes-from-anchor/src/v01/typeNodes/OptionTypeNode.ts b/packages/nodes-from-anchor/src/v01/typeNodes/OptionTypeNode.ts index 7ec73b314..a0f4e552d 100644 --- a/packages/nodes-from-anchor/src/v01/typeNodes/OptionTypeNode.ts +++ b/packages/nodes-from-anchor/src/v01/typeNodes/OptionTypeNode.ts @@ -1,16 +1,20 @@ import { numberTypeNode, OptionTypeNode, optionTypeNode } from '@codama/nodes'; -import { IdlV01TypeCOption, IdlV01TypeOption } from '../idl'; +import type { IdlV01TypeCOption, IdlV01TypeOption } from '../idl'; +import type { GenericsV01 } from '../unwrapGenerics'; import { typeNodeFromAnchorV01 } from './TypeNode'; -export function optionTypeNodeFromAnchorV01(idl: IdlV01TypeCOption | IdlV01TypeOption): OptionTypeNode { +export function optionTypeNodeFromAnchorV01( + idl: IdlV01TypeCOption | IdlV01TypeOption, + generics: GenericsV01, +): OptionTypeNode { const item = 'option' in idl ? idl.option : idl.coption; const hasOptionField = 'option' in idl; const prefix = numberTypeNode(hasOptionField ? 'u8' : 'u32'); const fixed = !hasOptionField; - return optionTypeNode(typeNodeFromAnchorV01(item), { + return optionTypeNode(typeNodeFromAnchorV01(item, generics), { fixed, prefix, }); diff --git a/packages/nodes-from-anchor/src/v01/typeNodes/StructFieldTypeNode.ts b/packages/nodes-from-anchor/src/v01/typeNodes/StructFieldTypeNode.ts index ea621bec5..7545a27d2 100644 --- a/packages/nodes-from-anchor/src/v01/typeNodes/StructFieldTypeNode.ts +++ b/packages/nodes-from-anchor/src/v01/typeNodes/StructFieldTypeNode.ts @@ -1,10 +1,14 @@ import { CODAMA_ERROR__ANCHOR__UNRECOGNIZED_IDL_TYPE, CodamaError } from '@codama/errors'; import { StructFieldTypeNode, structFieldTypeNode } from '@codama/nodes'; -import { IdlV01Field, IdlV01Type } from '../idl'; +import type { IdlV01Field, IdlV01Type } from '../idl'; +import type { GenericsV01 } from '../unwrapGenerics'; import { typeNodeFromAnchorV01 } from './TypeNode'; -export function structFieldTypeNodeFromAnchorV01(idl: IdlV01Field | IdlV01Type): StructFieldTypeNode { +export function structFieldTypeNodeFromAnchorV01( + idl: IdlV01Field | IdlV01Type, + generics: GenericsV01, +): StructFieldTypeNode { if (!isStructField(idl)) { throw new CodamaError(CODAMA_ERROR__ANCHOR__UNRECOGNIZED_IDL_TYPE, { idlType: JSON.stringify(idl), @@ -14,7 +18,7 @@ export function structFieldTypeNodeFromAnchorV01(idl: IdlV01Field | IdlV01Type): return structFieldTypeNode({ docs: idl.docs ?? [], name: idl.name, - type: typeNodeFromAnchorV01(idl.type), + type: typeNodeFromAnchorV01(idl.type, generics), }); } diff --git a/packages/nodes-from-anchor/src/v01/typeNodes/StructTypeNode.ts b/packages/nodes-from-anchor/src/v01/typeNodes/StructTypeNode.ts index aff965a9a..a71a24c47 100644 --- a/packages/nodes-from-anchor/src/v01/typeNodes/StructTypeNode.ts +++ b/packages/nodes-from-anchor/src/v01/typeNodes/StructTypeNode.ts @@ -1,10 +1,11 @@ import { StructTypeNode, structTypeNode } from '@codama/nodes'; -import { IdlV01DefinedFields, IdlV01TypeDefTyStruct } from '../idl'; +import type { IdlV01DefinedFields, IdlV01TypeDefTyStruct } from '../idl'; +import type { GenericsV01 } from '../unwrapGenerics'; import { structFieldTypeNodeFromAnchorV01 } from './StructFieldTypeNode'; -export function structTypeNodeFromAnchorV01(idl: IdlV01TypeDefTyStruct): StructTypeNode { +export function structTypeNodeFromAnchorV01(idl: IdlV01TypeDefTyStruct, generics: GenericsV01): StructTypeNode { const fields: IdlV01DefinedFields = idl.fields ?? []; - return structTypeNode(fields.map(structFieldTypeNodeFromAnchorV01)); + return structTypeNode(fields.map(field => structFieldTypeNodeFromAnchorV01(field, generics))); } diff --git a/packages/nodes-from-anchor/src/v01/typeNodes/TupleTypeNode.ts b/packages/nodes-from-anchor/src/v01/typeNodes/TupleTypeNode.ts index b578ba628..e7cd1510d 100644 --- a/packages/nodes-from-anchor/src/v01/typeNodes/TupleTypeNode.ts +++ b/packages/nodes-from-anchor/src/v01/typeNodes/TupleTypeNode.ts @@ -1,8 +1,9 @@ import { TupleTypeNode, tupleTypeNode } from '@codama/nodes'; -import { IdlV01DefinedFieldsTuple } from '../idl'; +import type { IdlV01DefinedFieldsTuple } from '../idl'; +import type { GenericsV01 } from '../unwrapGenerics'; import { typeNodeFromAnchorV01 } from './TypeNode'; -export function tupleTypeNodeFromAnchorV01(idl: IdlV01DefinedFieldsTuple): TupleTypeNode { - return tupleTypeNode(idl.map(typeNodeFromAnchorV01)); +export function tupleTypeNodeFromAnchorV01(idl: IdlV01DefinedFieldsTuple, generics: GenericsV01): TupleTypeNode { + return tupleTypeNode(idl.map(type => typeNodeFromAnchorV01(type, generics))); } diff --git a/packages/nodes-from-anchor/src/v01/typeNodes/TypeNode.ts b/packages/nodes-from-anchor/src/v01/typeNodes/TypeNode.ts index d16fd964b..dc3eefcc5 100644 --- a/packages/nodes-from-anchor/src/v01/typeNodes/TypeNode.ts +++ b/packages/nodes-from-anchor/src/v01/typeNodes/TypeNode.ts @@ -10,7 +10,7 @@ import { TypeNode, } from '@codama/nodes'; -import { +import type { IdlV01DefinedFields, IdlV01DefinedFieldsNamed, IdlV01DefinedFieldsTuple, @@ -18,6 +18,7 @@ import { IdlV01Type, IdlV01TypeDefTy, } from '../idl'; +import { type GenericsV01, unwrapGenericTypeFromAnchorV01 } from '../unwrapGenerics'; import { arrayTypeNodeFromAnchorV01 } from './ArrayTypeNode'; import { enumTypeNodeFromAnchorV01 } from './EnumTypeNode'; import { optionTypeNodeFromAnchorV01 } from './OptionTypeNode'; @@ -44,7 +45,7 @@ const IDL_V01_TYPE_LEAVES = [ 'shortU16', ] as const; -export const typeNodeFromAnchorV01 = (idlType: IdlV01Type | IdlV01TypeDefTy): TypeNode => { +export const typeNodeFromAnchorV01 = (idlType: IdlV01Type | IdlV01TypeDefTy, generics: GenericsV01): TypeNode => { // Leaf. if (typeof idlType === 'string' && IDL_V01_TYPE_LEAVES.includes(idlType)) { if (idlType === 'bool') return booleanTypeNode(); @@ -63,47 +64,53 @@ export const typeNodeFromAnchorV01 = (idlType: IdlV01Type | IdlV01TypeDefTy): Ty // Array. if ('array' in idlType && isArrayOfSize(idlType.array, 2)) { - return arrayTypeNodeFromAnchorV01(idlType); + return arrayTypeNodeFromAnchorV01(idlType, generics); } // Vec. if ('vec' in idlType) { - return arrayTypeNodeFromAnchorV01(idlType); + return arrayTypeNodeFromAnchorV01(idlType, generics); } // Defined link. - // TODO: Support generics. if ('defined' in idlType && typeof idlType.defined === 'object') { - return definedTypeLinkNode(idlType.defined.name); + return 'generics' in idlType.defined + ? unwrapGenericTypeFromAnchorV01(idlType, generics) + : definedTypeLinkNode(idlType.defined.name); + } + + // Generic reference. + if ('generic' in idlType) { + return typeNodeFromAnchorV01(generics.typeArgs[idlType.generic].type, generics); } // Enum. if ('kind' in idlType && idlType.kind === 'enum' && 'variants' in idlType) { - return enumTypeNodeFromAnchorV01(idlType); + return enumTypeNodeFromAnchorV01(idlType, generics); } // Alias. if ('kind' in idlType && idlType.kind === 'alias' && 'value' in idlType) { - return typeNodeFromAnchorV01(idlType.value); + return typeNodeFromAnchorV01(idlType.value, generics); } // Option. if ('option' in idlType) { - return optionTypeNodeFromAnchorV01(idlType); + return optionTypeNodeFromAnchorV01(idlType, generics); } if ('coption' in idlType) { - return optionTypeNodeFromAnchorV01(idlType); + return optionTypeNodeFromAnchorV01(idlType, generics); } // Struct and Tuple. if ('kind' in idlType && idlType.kind === 'struct') { const fields = idlType.fields ?? []; if (isStructFieldArray(fields)) { - return structTypeNodeFromAnchorV01(idlType); + return structTypeNodeFromAnchorV01(idlType, generics); } if (isTupleFieldArray(fields)) { - return tupleTypeNodeFromAnchorV01(fields); + return tupleTypeNodeFromAnchorV01(fields, generics); } } diff --git a/packages/nodes-from-anchor/src/v01/unwrapGenerics.ts b/packages/nodes-from-anchor/src/v01/unwrapGenerics.ts new file mode 100644 index 000000000..001ddfa02 --- /dev/null +++ b/packages/nodes-from-anchor/src/v01/unwrapGenerics.ts @@ -0,0 +1,63 @@ +import { CODAMA_ERROR__ANCHOR__GENERIC_TYPE_MISSING, CodamaError } from '@codama/errors'; +import { TypeNode } from '@codama/nodes'; + +import { + IdlV01GenericArgConst, + IdlV01GenericArgType, + IdlV01TypeDef, + IdlV01TypeDefGenericConst, + IdlV01TypeDefGenericType, + IdlV01TypeDefined, +} from './idl'; +import { typeNodeFromAnchorV01 } from './typeNodes'; + +export type GenericsV01 = { + constArgs: Record; + typeArgs: Record; + types: Record>>; +}; + +export function extractGenerics(types: IdlV01TypeDef[]): [IdlV01TypeDef[], GenericsV01] { + const [nonGenericTypes, genericTypes] = types.reduce( + (acc, type) => { + acc['generics' in type ? 1 : 0].push(type); + return acc; + }, + [[], []] as [IdlV01TypeDef[], IdlV01TypeDef[]], + ); + + const generics = { + constArgs: {}, + typeArgs: {}, + types: Object.fromEntries(genericTypes.map(type => [type.name, type])), + } as GenericsV01; + + return [nonGenericTypes, generics]; +} + +export function unwrapGenericTypeFromAnchorV01(type: IdlV01TypeDefined, generics: GenericsV01): TypeNode { + const genericType = generics.types[type.defined.name]; + if (!genericType) { + throw new CodamaError(CODAMA_ERROR__ANCHOR__GENERIC_TYPE_MISSING, { name: type.defined.name }); + } + + const constArgs: GenericsV01['constArgs'] = {}; + const typeArgs: GenericsV01['typeArgs'] = {}; + + const genericDefinitions = genericType.generics ?? []; + const genericArgs = type.defined.generics ?? []; + genericDefinitions.forEach((genericDefinition, index) => { + const genericArg = genericArgs[index]; + if (genericDefinition.kind === 'const') { + constArgs[genericDefinition.name] = genericArg as IdlV01GenericArgConst & IdlV01TypeDefGenericConst; + } else { + typeArgs[genericDefinition.name] = genericArg as IdlV01GenericArgType & IdlV01TypeDefGenericType; + } + }); + + return typeNodeFromAnchorV01(genericType.type, { + ...generics, + constArgs: { ...generics.constArgs, ...constArgs }, + typeArgs: { ...generics.typeArgs, ...typeArgs }, + }); +} diff --git a/packages/nodes-from-anchor/test/v01/AccountNode.test.ts b/packages/nodes-from-anchor/test/v01/AccountNode.test.ts index 636fd6354..b09bdc6c4 100644 --- a/packages/nodes-from-anchor/test/v01/AccountNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/AccountNode.test.ts @@ -9,7 +9,9 @@ import { } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { accountNodeFromAnchorV01, getAnchorDiscriminatorV01 } from '../../src'; +import { accountNodeFromAnchorV01, GenericsV01, getAnchorDiscriminatorV01 } from '../../src'; + +const generics = {} as GenericsV01; test('it creates account nodes with anchor discriminators', () => { const node = accountNodeFromAnchorV01( @@ -32,6 +34,7 @@ test('it creates account nodes with anchor discriminators', () => { }, }, ], + generics, ); expect(node).toEqual( diff --git a/packages/nodes-from-anchor/test/v01/DefinedTypeNode.test.ts b/packages/nodes-from-anchor/test/v01/DefinedTypeNode.test.ts index 1cebd4457..08e625210 100644 --- a/packages/nodes-from-anchor/test/v01/DefinedTypeNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/DefinedTypeNode.test.ts @@ -1,16 +1,20 @@ -import { definedTypeNode, numberTypeNode, structFieldTypeNode, structTypeNode } from '@codama/nodes'; +/* eslint-disable sort-keys-fix/sort-keys-fix */ +import { definedTypeNode, numberTypeNode, structFieldTypeNode, structTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { definedTypeNodeFromAnchorV01 } from '../../src'; +import { definedTypeNodeFromAnchorV01, GenericsV01 } from '../../src'; test('it creates defined type nodes', () => { - const node = definedTypeNodeFromAnchorV01({ - name: 'MyType', - type: { - fields: [{ name: 'my_field', type: 'u64' }], - kind: 'struct', + const node = definedTypeNodeFromAnchorV01( + { + name: 'MyType', + type: { + fields: [{ name: 'my_field', type: 'u64' }], + kind: 'struct', + }, }, - }); + {} as GenericsV01, + ); expect(node).toEqual( definedTypeNode({ @@ -24,3 +28,76 @@ test('it creates defined type nodes', () => { }), ); }); + +test('it unwraps generic arguments', () => { + const node = definedTypeNodeFromAnchorV01( + { + name: 'Buffer', + type: { + fields: [{ name: 'data', type: { generic: 'T' } }], + kind: 'struct', + }, + }, + { + constArgs: {}, + typeArgs: { T: { name: 'T', kind: 'type', type: 'u64' } }, + types: {}, + }, + ); + + expect(node).toEqual( + definedTypeNode({ + name: 'buffer', + type: structTypeNode([ + structFieldTypeNode({ + name: 'data', + type: numberTypeNode('u64'), + }), + ]), + }), + ); +}); + +test('it unwraps nested generic types', () => { + const node = definedTypeNodeFromAnchorV01( + { + name: 'Buffer', + type: { + fields: [{ name: 'data', type: { generic: 'T' } }], + kind: 'struct', + }, + }, + { + constArgs: {}, + typeArgs: { + T: { + name: 'T', + kind: 'type', + type: { defined: { name: 'PrefixedData', generics: [{ kind: 'type', type: 'u8' }] } }, + }, + }, + types: { + PrefixedData: { + name: 'PrefixedData', + generics: [{ name: 'T', kind: 'type' }], + type: { + fields: ['u64', { generic: 'T' }], + kind: 'struct', + }, + }, + }, + }, + ); + + expect(node).toEqual( + definedTypeNode({ + name: 'buffer', + type: structTypeNode([ + structFieldTypeNode({ + name: 'data', + type: tupleTypeNode([numberTypeNode('u64'), numberTypeNode('u8')]), + }), + ]), + }), + ); +}); diff --git a/packages/nodes-from-anchor/test/v01/InstructionArgumentNode.test.ts b/packages/nodes-from-anchor/test/v01/InstructionArgumentNode.test.ts index 3c66e103d..5be1841b2 100644 --- a/packages/nodes-from-anchor/test/v01/InstructionArgumentNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/InstructionArgumentNode.test.ts @@ -1,13 +1,18 @@ import { instructionArgumentNode, numberTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { instructionArgumentNodeFromAnchorV01 } from '../../src'; +import { GenericsV01, instructionArgumentNodeFromAnchorV01 } from '../../src'; + +const generics = {} as GenericsV01; test('it creates instruction argument nodes', () => { - const node = instructionArgumentNodeFromAnchorV01({ - name: 'my_instruction_argument', - type: 'u8', - }); + const node = instructionArgumentNodeFromAnchorV01( + { + name: 'my_instruction_argument', + type: 'u8', + }, + generics, + ); expect(node).toEqual( instructionArgumentNode({ diff --git a/packages/nodes-from-anchor/test/v01/InstructionNode.test.ts b/packages/nodes-from-anchor/test/v01/InstructionNode.test.ts index 129244ac4..98f17c6ce 100644 --- a/packages/nodes-from-anchor/test/v01/InstructionNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/InstructionNode.test.ts @@ -13,7 +13,9 @@ import { } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getAnchorDiscriminatorV01, instructionNodeFromAnchorV01 } from '../../src'; +import { GenericsV01, getAnchorDiscriminatorV01, instructionNodeFromAnchorV01 } from '../../src'; + +const generics = {} as GenericsV01; test('it creates instruction nodes', () => { const node = instructionNodeFromAnchorV01( @@ -50,6 +52,7 @@ test('it creates instruction nodes', () => { discriminator: [246, 28, 6, 87, 251, 45, 50, 42], name: 'mintTokens', }, + generics, ); expect(node).toEqual( @@ -88,12 +91,16 @@ test('it creates instruction nodes', () => { }); test('it creates instruction nodes with anchor discriminators', () => { - const node = instructionNodeFromAnchorV01([], { - accounts: [], - args: [], - discriminator: [246, 28, 6, 87, 251, 45, 50, 42], - name: 'myInstruction', - }); + const node = instructionNodeFromAnchorV01( + [], + { + accounts: [], + args: [], + discriminator: [246, 28, 6, 87, 251, 45, 50, 42], + name: 'myInstruction', + }, + generics, + ); expect(node).toEqual( instructionNode({ diff --git a/packages/nodes-from-anchor/test/v01/ProgramNode.test.ts b/packages/nodes-from-anchor/test/v01/ProgramNode.test.ts index 3de49aac8..552a035f4 100644 --- a/packages/nodes-from-anchor/test/v01/ProgramNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/ProgramNode.test.ts @@ -2,10 +2,17 @@ import { accountNode, accountValueNode, argumentValueNode, + arrayTypeNode, bytesTypeNode, constantPdaSeedNodeFromBytes, + definedTypeLinkNode, + definedTypeNode, + enumEmptyVariantTypeNode, + enumTupleVariantTypeNode, + enumTypeNode, errorNode, fieldDiscriminatorNode, + fixedCountNode, fixedSizeTypeNode, instructionAccountNode, instructionArgumentNode, @@ -18,6 +25,7 @@ import { publicKeyTypeNode, structFieldTypeNode, structTypeNode, + tupleTypeNode, variablePdaSeedNode, } from '@codama/nodes'; import { expect, test } from 'vitest'; @@ -146,3 +154,156 @@ test('it creates program nodes', () => { }), ); }); + +test('it unwraps and removes generic types', () => { + const node = programNodeFromAnchorV01({ + address: '1111', + instructions: [], + metadata: { name: 'my_program', spec: '0.1.0', version: '1.2.3' }, + types: [ + { + generics: [ + { kind: 'const', name: 'N', type: 'usize' }, + { kind: 'type', name: 'T' }, + ], + name: 'SimpleAllocator', + type: { + fields: [ + { + name: 'state', + type: { array: [{ defined: { name: 'ItemState' } }, { generic: 'N' }] }, + }, + { + name: 'data', + type: { array: [{ generic: 'T' }, { generic: 'N' }] }, + }, + ], + kind: 'struct', + }, + }, + { + name: 'AccountData', + type: { + kind: 'enum', + variants: [ + { name: 'Unknown' }, + { + fields: [ + { + defined: { + generics: [ + { kind: 'const', value: '1000' }, + { kind: 'type', type: { defined: { name: 'VirtualTimelockAccount' } } }, + ], + name: 'SimpleAllocator', + }, + }, + ], + name: 'Timelock', + }, + { + fields: [ + { + defined: { + generics: [ + { kind: 'const', value: '500' }, + { kind: 'type', type: { defined: { name: 'VirtualDurableNonce' } } }, + ], + name: 'SimpleAllocator', + }, + }, + ], + name: 'Nonce', + }, + { + fields: [ + { + defined: { + generics: [ + { kind: 'const', value: '250' }, + { kind: 'type', type: { defined: { name: 'VirtualRelayAccount' } } }, + ], + name: 'SimpleAllocator', + }, + }, + ], + name: 'Relay', + }, + ], + }, + }, + ], + }); + + expect(node).toEqual( + programNode({ + definedTypes: [ + definedTypeNode({ + name: 'AccountData', + type: enumTypeNode([ + enumEmptyVariantTypeNode('unknown'), + enumTupleVariantTypeNode( + 'timelock', + tupleTypeNode([ + structTypeNode([ + structFieldTypeNode({ + name: 'state', + type: arrayTypeNode(definedTypeLinkNode('itemState'), fixedCountNode(1000)), + }), + structFieldTypeNode({ + name: 'data', + type: arrayTypeNode( + definedTypeLinkNode('virtualTimelockAccount'), + fixedCountNode(1000), + ), + }), + ]), + ]), + ), + enumTupleVariantTypeNode( + 'nonce', + tupleTypeNode([ + structTypeNode([ + structFieldTypeNode({ + name: 'state', + type: arrayTypeNode(definedTypeLinkNode('itemState'), fixedCountNode(500)), + }), + structFieldTypeNode({ + name: 'data', + type: arrayTypeNode( + definedTypeLinkNode('virtualDurableNonce'), + fixedCountNode(500), + ), + }), + ]), + ]), + ), + enumTupleVariantTypeNode( + 'relay', + tupleTypeNode([ + structTypeNode([ + structFieldTypeNode({ + name: 'state', + type: arrayTypeNode(definedTypeLinkNode('itemState'), fixedCountNode(250)), + }), + structFieldTypeNode({ + name: 'data', + type: arrayTypeNode( + definedTypeLinkNode('virtualRelayAccount'), + fixedCountNode(250), + ), + }), + ]), + ]), + ), + ]), + }), + ], + name: 'myProgram', + origin: 'anchor', + pdas: [], + publicKey: '1111', + version: '1.2.3', + }), + ); +}); diff --git a/packages/nodes-from-anchor/test/v01/typeNodes/ArrayTypeNode.test.ts b/packages/nodes-from-anchor/test/v01/typeNodes/ArrayTypeNode.test.ts index 9c0ef061f..3b44cabcb 100644 --- a/packages/nodes-from-anchor/test/v01/typeNodes/ArrayTypeNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/typeNodes/ArrayTypeNode.test.ts @@ -1,11 +1,28 @@ import { arrayTypeNode, fixedCountNode, numberTypeNode, prefixedCountNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { typeNodeFromAnchorV01 } from '../../../src'; +import { GenericsV01, typeNodeFromAnchorV01 } from '../../../src'; + +const generics = {} as GenericsV01; test('it creates array type nodes', () => { - expect(typeNodeFromAnchorV01({ array: ['u8', 2] })).toEqual(arrayTypeNode(numberTypeNode('u8'), fixedCountNode(2))); - expect(typeNodeFromAnchorV01({ vec: 'u8' })).toEqual( + expect(typeNodeFromAnchorV01({ array: ['u8', 2] }, generics)).toEqual( + arrayTypeNode(numberTypeNode('u8'), fixedCountNode(2)), + ); + expect(typeNodeFromAnchorV01({ vec: 'u8' }, generics)).toEqual( arrayTypeNode(numberTypeNode('u8'), prefixedCountNode(numberTypeNode('u32'))), ); }); + +test('it unwraps array nodes with generic sizes', () => { + expect( + typeNodeFromAnchorV01( + { array: ['u8', { generic: 'N' }] }, + { + constArgs: { N: { kind: 'const', name: 'N', type: 'usize', value: '100' } }, + typeArgs: {}, + types: {}, + }, + ), + ).toEqual(arrayTypeNode(numberTypeNode('u8'), fixedCountNode(100))); +}); diff --git a/packages/nodes-from-anchor/test/v01/typeNodes/BooleanTypeNode.test.ts b/packages/nodes-from-anchor/test/v01/typeNodes/BooleanTypeNode.test.ts index 6c6feb2bd..172c447a5 100644 --- a/packages/nodes-from-anchor/test/v01/typeNodes/BooleanTypeNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/typeNodes/BooleanTypeNode.test.ts @@ -1,8 +1,10 @@ import { booleanTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { typeNodeFromAnchorV01 } from '../../../src'; +import { GenericsV01, typeNodeFromAnchorV01 } from '../../../src'; + +const generics = {} as GenericsV01; test('it creates boolean type nodes', () => { - expect(typeNodeFromAnchorV01('bool')).toEqual(booleanTypeNode()); + expect(typeNodeFromAnchorV01('bool', generics)).toEqual(booleanTypeNode()); }); diff --git a/packages/nodes-from-anchor/test/v01/typeNodes/BytesTypeNode.test.ts b/packages/nodes-from-anchor/test/v01/typeNodes/BytesTypeNode.test.ts index 0fc7eaae8..8f1303d2b 100644 --- a/packages/nodes-from-anchor/test/v01/typeNodes/BytesTypeNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/typeNodes/BytesTypeNode.test.ts @@ -1,8 +1,12 @@ import { bytesTypeNode, numberTypeNode, sizePrefixTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { typeNodeFromAnchorV01 } from '../../../src'; +import { GenericsV01, typeNodeFromAnchorV01 } from '../../../src'; + +const generics = {} as GenericsV01; test('it creates bytes type nodes', () => { - expect(typeNodeFromAnchorV01('bytes')).toEqual(sizePrefixTypeNode(bytesTypeNode(), numberTypeNode('u32'))); + expect(typeNodeFromAnchorV01('bytes', generics)).toEqual( + sizePrefixTypeNode(bytesTypeNode(), numberTypeNode('u32')), + ); }); diff --git a/packages/nodes-from-anchor/test/v01/typeNodes/EnumTypeNode.test.ts b/packages/nodes-from-anchor/test/v01/typeNodes/EnumTypeNode.test.ts index a663dd6ba..e78b859da 100644 --- a/packages/nodes-from-anchor/test/v01/typeNodes/EnumTypeNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/typeNodes/EnumTypeNode.test.ts @@ -11,17 +11,22 @@ import { } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { typeNodeFromAnchorV01 } from '../../../src'; +import { GenericsV01, typeNodeFromAnchorV01 } from '../../../src'; + +const generics = {} as GenericsV01; test('it creates enum type nodes', () => { - const node = typeNodeFromAnchorV01({ - kind: 'enum', - variants: [ - { name: 'variantA' }, // Empty variant. - { fields: ['u16', 'bool'], name: 'variantB' }, // Tuple variant. - { fields: [{ name: 'age', type: 'u8' }], name: 'variantC' }, // Struct variant. - ], - }); + const node = typeNodeFromAnchorV01( + { + kind: 'enum', + variants: [ + { name: 'variantA' }, // Empty variant. + { fields: ['u16', 'bool'], name: 'variantB' }, // Tuple variant. + { fields: [{ name: 'age', type: 'u8' }], name: 'variantC' }, // Struct variant. + ], + }, + generics, + ); expect(node).toEqual( enumTypeNode([ diff --git a/packages/nodes-from-anchor/test/v01/typeNodes/NumberTypeNode.test.ts b/packages/nodes-from-anchor/test/v01/typeNodes/NumberTypeNode.test.ts index c5b6c8f04..6e1fb2b40 100644 --- a/packages/nodes-from-anchor/test/v01/typeNodes/NumberTypeNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/typeNodes/NumberTypeNode.test.ts @@ -1,20 +1,22 @@ import { numberTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { typeNodeFromAnchorV01 } from '../../../src'; +import { GenericsV01, typeNodeFromAnchorV01 } from '../../../src'; + +const generics = {} as GenericsV01; test('it creates number type nodes', () => { - expect(typeNodeFromAnchorV01('f32')).toEqual(numberTypeNode('f32')); - expect(typeNodeFromAnchorV01('f64')).toEqual(numberTypeNode('f64')); - expect(typeNodeFromAnchorV01('i8')).toEqual(numberTypeNode('i8')); - expect(typeNodeFromAnchorV01('i16')).toEqual(numberTypeNode('i16')); - expect(typeNodeFromAnchorV01('i32')).toEqual(numberTypeNode('i32')); - expect(typeNodeFromAnchorV01('i64')).toEqual(numberTypeNode('i64')); - expect(typeNodeFromAnchorV01('i128')).toEqual(numberTypeNode('i128')); - expect(typeNodeFromAnchorV01('shortU16')).toEqual(numberTypeNode('shortU16')); - expect(typeNodeFromAnchorV01('u8')).toEqual(numberTypeNode('u8')); - expect(typeNodeFromAnchorV01('u16')).toEqual(numberTypeNode('u16')); - expect(typeNodeFromAnchorV01('u32')).toEqual(numberTypeNode('u32')); - expect(typeNodeFromAnchorV01('u64')).toEqual(numberTypeNode('u64')); - expect(typeNodeFromAnchorV01('u128')).toEqual(numberTypeNode('u128')); + expect(typeNodeFromAnchorV01('f32', generics)).toEqual(numberTypeNode('f32')); + expect(typeNodeFromAnchorV01('f64', generics)).toEqual(numberTypeNode('f64')); + expect(typeNodeFromAnchorV01('i8', generics)).toEqual(numberTypeNode('i8')); + expect(typeNodeFromAnchorV01('i16', generics)).toEqual(numberTypeNode('i16')); + expect(typeNodeFromAnchorV01('i32', generics)).toEqual(numberTypeNode('i32')); + expect(typeNodeFromAnchorV01('i64', generics)).toEqual(numberTypeNode('i64')); + expect(typeNodeFromAnchorV01('i128', generics)).toEqual(numberTypeNode('i128')); + expect(typeNodeFromAnchorV01('shortU16', generics)).toEqual(numberTypeNode('shortU16')); + expect(typeNodeFromAnchorV01('u8', generics)).toEqual(numberTypeNode('u8')); + expect(typeNodeFromAnchorV01('u16', generics)).toEqual(numberTypeNode('u16')); + expect(typeNodeFromAnchorV01('u32', generics)).toEqual(numberTypeNode('u32')); + expect(typeNodeFromAnchorV01('u64', generics)).toEqual(numberTypeNode('u64')); + expect(typeNodeFromAnchorV01('u128', generics)).toEqual(numberTypeNode('u128')); }); diff --git a/packages/nodes-from-anchor/test/v01/typeNodes/OptionTypeNode.test.ts b/packages/nodes-from-anchor/test/v01/typeNodes/OptionTypeNode.test.ts index 7803c0c62..0d488d91b 100644 --- a/packages/nodes-from-anchor/test/v01/typeNodes/OptionTypeNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/typeNodes/OptionTypeNode.test.ts @@ -1,17 +1,19 @@ import { numberTypeNode, optionTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { typeNodeFromAnchorV01 } from '../../../src'; +import { GenericsV01, typeNodeFromAnchorV01 } from '../../../src'; + +const generics = {} as GenericsV01; test('it creates option type nodes', () => { - expect(typeNodeFromAnchorV01({ option: 'u8' })).toEqual(optionTypeNode(numberTypeNode('u8'))); + expect(typeNodeFromAnchorV01({ option: 'u8' }, generics)).toEqual(optionTypeNode(numberTypeNode('u8'))); }); test('it creates option type nodes with fixed size', () => { - expect(typeNodeFromAnchorV01({ coption: 'u8' })).toEqual( + expect(typeNodeFromAnchorV01({ coption: 'u8' }, generics)).toEqual( optionTypeNode(numberTypeNode('u8'), { fixed: true, prefix: numberTypeNode('u32') }), ); - expect(typeNodeFromAnchorV01({ coption: 'u8' })).toEqual( + expect(typeNodeFromAnchorV01({ coption: 'u8' }, generics)).toEqual( optionTypeNode(numberTypeNode('u8'), { fixed: true, prefix: numberTypeNode('u32') }), ); }); diff --git a/packages/nodes-from-anchor/test/v01/typeNodes/PublicKeyTypeNode.test.ts b/packages/nodes-from-anchor/test/v01/typeNodes/PublicKeyTypeNode.test.ts index 3c74e4987..c910a208f 100644 --- a/packages/nodes-from-anchor/test/v01/typeNodes/PublicKeyTypeNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/typeNodes/PublicKeyTypeNode.test.ts @@ -1,8 +1,10 @@ import { publicKeyTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { typeNodeFromAnchorV01 } from '../../../src'; +import { GenericsV01, typeNodeFromAnchorV01 } from '../../../src'; + +const generics = {} as GenericsV01; test('it creates public key type nodes', () => { - expect(typeNodeFromAnchorV01('pubkey')).toEqual(publicKeyTypeNode()); + expect(typeNodeFromAnchorV01('pubkey', generics)).toEqual(publicKeyTypeNode()); }); diff --git a/packages/nodes-from-anchor/test/v01/typeNodes/StringTypeNode.test.ts b/packages/nodes-from-anchor/test/v01/typeNodes/StringTypeNode.test.ts index 6391562ed..cea230a4c 100644 --- a/packages/nodes-from-anchor/test/v01/typeNodes/StringTypeNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/typeNodes/StringTypeNode.test.ts @@ -1,8 +1,12 @@ import { numberTypeNode, sizePrefixTypeNode, stringTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { typeNodeFromAnchorV01 } from '../../../src'; +import { GenericsV01, typeNodeFromAnchorV01 } from '../../../src'; + +const generics = {} as GenericsV01; test('it creates string type nodes', () => { - expect(typeNodeFromAnchorV01('string')).toEqual(sizePrefixTypeNode(stringTypeNode('utf8'), numberTypeNode('u32'))); + expect(typeNodeFromAnchorV01('string', generics)).toEqual( + sizePrefixTypeNode(stringTypeNode('utf8'), numberTypeNode('u32')), + ); }); diff --git a/packages/nodes-from-anchor/test/v01/typeNodes/StructTypeNode.test.ts b/packages/nodes-from-anchor/test/v01/typeNodes/StructTypeNode.test.ts index f8ba70f3c..c3bdfceb9 100644 --- a/packages/nodes-from-anchor/test/v01/typeNodes/StructTypeNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/typeNodes/StructTypeNode.test.ts @@ -12,17 +12,22 @@ import { } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { typeNodeFromAnchorV01 } from '../../../src'; +import { GenericsV01, typeNodeFromAnchorV01 } from '../../../src'; + +const generics = {} as GenericsV01; test('it creates struct type nodes', () => { - const node = typeNodeFromAnchorV01({ - fields: [ - { name: 'name', type: 'string' }, - { name: 'age', type: 'u8' }, - { name: 'created_at', type: 'u8' }, - ], - kind: 'struct', - }); + const node = typeNodeFromAnchorV01( + { + fields: [ + { name: 'name', type: 'string' }, + { name: 'age', type: 'u8' }, + { name: 'created_at', type: 'u8' }, + ], + kind: 'struct', + }, + generics, + ); expect(node).toEqual( structTypeNode([ @@ -37,10 +42,13 @@ test('it creates struct type nodes', () => { }); test('it creates tuple type nodes when unnamed fields are provided', () => { - const node = typeNodeFromAnchorV01({ - fields: ['u8', { vec: 'pubkey' }], - kind: 'struct', - }); + const node = typeNodeFromAnchorV01( + { + fields: ['u8', { vec: 'pubkey' }], + kind: 'struct', + }, + generics, + ); expect(node).toEqual( tupleTypeNode([ diff --git a/packages/nodes-from-anchor/test/v01/typeNodes/TupleTypeNode.test.ts b/packages/nodes-from-anchor/test/v01/typeNodes/TupleTypeNode.test.ts index 2feb74687..79139119e 100644 --- a/packages/nodes-from-anchor/test/v01/typeNodes/TupleTypeNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/typeNodes/TupleTypeNode.test.ts @@ -1,13 +1,18 @@ import { numberTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { typeNodeFromAnchorV01 } from '../../../src'; +import { GenericsV01, typeNodeFromAnchorV01 } from '../../../src'; + +const generics = {} as GenericsV01; test('it creates tuple type nodes', () => { - const node = typeNodeFromAnchorV01({ - fields: ['u8', 'pubkey'], - kind: 'struct', - }); + const node = typeNodeFromAnchorV01( + { + fields: ['u8', 'pubkey'], + kind: 'struct', + }, + generics, + ); expect(node).toEqual(tupleTypeNode([numberTypeNode('u8'), publicKeyTypeNode()])); });