diff --git a/lang/syn/src/idl/file.rs b/lang/syn/src/idl/file.rs index 9b5c2b3c49..d95695f363 100644 --- a/lang/syn/src/idl/file.rs +++ b/lang/syn/src/idl/file.rs @@ -4,7 +4,6 @@ use crate::parser::{self, accounts, error, program}; use crate::Ty; use crate::{AccountField, AccountsStruct, StateIx}; use anyhow::Result; -use heck::MixedCase; use quote::ToTokens; use std::collections::{HashMap, HashSet}; use std::path::Path; @@ -44,7 +43,7 @@ pub fn parse( methods .iter() .map(|method: &StateIx| { - let name = method.ident.to_string().to_mixed_case(); + let name = method.ident.to_string(); let args = method .args .iter() @@ -53,7 +52,7 @@ pub fn parse( arg.raw_arg.ty.to_tokens(&mut tts); let ty = tts.to_string().parse().unwrap(); IdlField { - name: arg.name.to_string().to_mixed_case(), + name: arg.name.to_string(), ty, } }) @@ -93,7 +92,7 @@ pub fn parse( arg_typed.ty.to_tokens(&mut tts); let ty = tts.to_string().parse().unwrap(); IdlField { - name: parser::tts_to_string(&arg_typed.pat).to_mixed_case(), + name: parser::tts_to_string(&arg_typed.pat), ty, } } @@ -122,7 +121,7 @@ pub fn parse( f.ty.to_tokens(&mut tts); let ty = tts.to_string().parse().unwrap(); IdlField { - name: f.ident.as_ref().unwrap().to_string().to_mixed_case(), + name: f.ident.as_ref().unwrap().to_string(), ty, } }) @@ -159,7 +158,7 @@ pub fn parse( .args .iter() .map(|arg| IdlField { - name: arg.name.to_string().to_mixed_case(), + name: arg.name.to_string(), ty: to_idl_type(&ctx, &arg.raw_arg.ty), }) .collect::>(); @@ -172,7 +171,7 @@ pub fn parse( _ => Some(ret_type_str.parse().unwrap()), }; IdlInstruction { - name: ix.ident.to_string().to_mixed_case(), + name: ix.ident.to_string(), accounts, args, returns, @@ -196,7 +195,7 @@ pub fn parse( Some(i) => parser::tts_to_string(&i.path) == "index", }; IdlEventField { - name: f.ident.clone().unwrap().to_string().to_mixed_case(), + name: f.ident.clone().unwrap().to_string(), ty: to_idl_type(&ctx, &f.ty), index, } @@ -413,7 +412,7 @@ fn parse_ty_defs(ctx: &CrateContext) -> Result> { .iter() .map(|f: &syn::Field| { Ok(IdlField { - name: f.ident.as_ref().unwrap().to_string().to_mixed_case(), + name: f.ident.as_ref().unwrap().to_string(), ty: to_idl_type(ctx, &f.ty), }) }) @@ -552,12 +551,12 @@ fn idl_accounts( }); let accounts = idl_accounts(ctx, accs_strct, global_accs, seeds_feature); IdlAccountItem::IdlAccounts(IdlAccounts { - name: comp_f.ident.to_string().to_mixed_case(), + name: comp_f.ident.to_string(), accounts, }) } AccountField::Field(acc) => IdlAccountItem::IdlAccount(IdlAccount { - name: acc.ident.to_string().to_mixed_case(), + name: acc.ident.to_string(), is_mut: acc.constraints.is_mutable(), is_signer: match acc.ty { Ty::Signer => true, diff --git a/lang/syn/src/idl/mod.rs b/lang/syn/src/idl/mod.rs index 50bafb8953..2d3ea8ead7 100644 --- a/lang/syn/src/idl/mod.rs +++ b/lang/syn/src/idl/mod.rs @@ -50,7 +50,7 @@ pub struct IdlInstruction { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] + pub struct IdlAccounts { pub name: String, pub accounts: Vec, @@ -64,7 +64,7 @@ pub enum IdlAccountItem { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] + pub struct IdlAccount { pub name: String, pub is_mut: bool, @@ -74,7 +74,6 @@ pub struct IdlAccount { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] pub struct IdlPda { pub seeds: Vec, #[serde(skip_serializing_if = "Option::is_none", default)] @@ -82,7 +81,6 @@ pub struct IdlPda { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase", tag = "kind")] pub enum IdlSeed { Const(IdlSeedConst), Arg(IdlSeedArg), @@ -90,7 +88,6 @@ pub enum IdlSeed { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] pub struct IdlSeedAccount { #[serde(rename = "type")] pub ty: IdlType, @@ -102,7 +99,6 @@ pub struct IdlSeedAccount { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] pub struct IdlSeedArg { #[serde(rename = "type")] pub ty: IdlType, @@ -110,7 +106,6 @@ pub struct IdlSeedArg { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] pub struct IdlSeedConst { #[serde(rename = "type")] pub ty: IdlType, @@ -167,7 +162,7 @@ pub enum EnumFields { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] + pub enum IdlType { Bool, U8, diff --git a/tests/misc/tests/misc/misc.ts b/tests/misc/tests/misc/misc.ts index 22dc4ee065..4d15fc5c56 100644 --- a/tests/misc/tests/misc/misc.ts +++ b/tests/misc/tests/misc/misc.ts @@ -30,6 +30,7 @@ describe("misc", () => { const wallet = provider.wallet as Wallet; anchor.setProvider(provider); const program = anchor.workspace.Misc as Program; + const miscIdl = program.idl; const misc2Program = anchor.workspace.Misc2 as Program; it("Can allocate extra space for a state constructor", async () => { @@ -965,6 +966,10 @@ describe("misc", () => { }); it("Should not include NO_IDL const in IDL", async () => { + // The type system sees that this constant does not exist and the + // ts-expect-error makes sure that ts compilation fails if that ever + // changes. + // @ts-expect-error assert.isUndefined(miscIdl.constants.find((c) => c.name === "NO_IDL")); }); diff --git a/ts/src/idl.ts b/ts/src/idl.ts index efda32c172..bfb4584a2b 100644 --- a/ts/src/idl.ts +++ b/ts/src/idl.ts @@ -1,7 +1,21 @@ +import camelCase from "camelcase"; import { Buffer } from "buffer"; import { PublicKey } from "@solana/web3.js"; import * as borsh from "@project-serum/borsh"; +export type RawIdl = { + version: string; + name: string; + instructions: RawIdlInstruction[]; + state?: RawIdlState; + accounts?: RawIdlAccountDef[]; + types?: RawIdlTypeDef[]; + events?: RawIdlEvent[]; + errors?: IdlErrorCode[]; + constants?: RawIdlConstant[]; + metadata?: IdlMetadata; +}; + export type Idl = { version: string; name: string; @@ -12,20 +26,38 @@ export type Idl = { events?: IdlEvent[]; errors?: IdlErrorCode[]; constants?: IdlConstant[]; + camelized?: boolean; metadata?: IdlMetadata; }; +type RawIdlEvent = { + name: string; + fields: RawIdlEventField[]; +}; + +export type IdlEvent = { + name: string; + fields: IdlEventField[]; +}; + export type IdlMetadata = any; +export type RawIdlConstant = { + name: string; + type: RawIdlType; + value: string; +}; + export type IdlConstant = { name: string; type: IdlType; value: string; }; -export type IdlEvent = { +type RawIdlEventField = { name: string; - fields: IdlEventField[]; + type: RawIdlType; + index: boolean; }; export type IdlEventField = { @@ -34,6 +66,12 @@ export type IdlEventField = { index: boolean; }; +type RawIdlInstruction = { + name: string; + accounts: RawIdlAccountItem[]; + args: RawIdlField[]; +}; + export type IdlInstruction = { name: string; accounts: IdlAccountItem[]; @@ -41,15 +79,31 @@ export type IdlInstruction = { returns?: IdlType; }; +type RawIdlState = { + struct: RawIdlTypeDef; + methods: RawIdlStateMethod[]; +}; + export type IdlState = { struct: IdlTypeDef; methods: IdlStateMethod[]; }; +type RawIdlStateMethod = RawIdlInstruction; + export type IdlStateMethod = IdlInstruction; +type RawIdlAccountItem = RawIdlAccount | RawIdlAccounts; + export type IdlAccountItem = IdlAccount | IdlAccounts; +type RawIdlAccount = { + name: string; + is_mut: boolean; + is_signer: boolean; + pda?: RawIdlPda; +}; + export type IdlAccount = { name: string; isMut: boolean; @@ -57,6 +111,17 @@ export type IdlAccount = { pda?: IdlPda; }; +// A nested/recursive version of RawIdlAccount. +type RawIdlAccounts = { + name: string; + accounts: RawIdlAccountItem[]; +}; + +type RawIdlPda = { + seeds: IdlSeed[]; + program_id?: IdlSeed; +}; + export type IdlPda = { seeds: IdlSeed[]; programId?: IdlSeed; @@ -70,36 +135,89 @@ export type IdlAccounts = { accounts: IdlAccountItem[]; }; +type RawIdlField = { + name: string; + type: RawIdlType; +}; + export type IdlField = { name: string; type: IdlType; }; +type RawIdlTypeDef = { + name: string; + type: RawIdlTypeDefTy; +}; + export type IdlTypeDef = { name: string; type: IdlTypeDefTy; }; +type RawIdlTypeDefTyStruct = { + kind: "struct"; + fields: RawIdlTypeDefStruct; +}; + export type IdlAccountDef = { name: string; type: IdlTypeDefTyStruct; }; +type RawIdlAccountDef = { + name: string; + type: RawIdlTypeDefTyStruct; +}; + export type IdlTypeDefTyStruct = { kind: "struct"; fields: IdlTypeDefStruct; }; +type RawIdlTypeDefTyEnum = { + kind: "enum"; + variants: RawIdlEnumVariant[]; +}; + export type IdlTypeDefTyEnum = { kind: "enum"; variants: IdlEnumVariant[]; }; +type RawIdlTypeDefTy = RawIdlTypeDefTyEnum | RawIdlTypeDefTyStruct; + type IdlTypeDefTy = IdlTypeDefTyEnum | IdlTypeDefTyStruct; +type RawIdlTypeDefStruct = Array; + type IdlTypeDefStruct = Array; -export type IdlType = +type RawLiteralIdlType = + | "Bool" + | "U8" + | "I8" + | "U16" + | "I16" + | "U32" + | "I32" + | "U64" + | "I64" + | "U128" + | "I128" + | "Bytes" + | "String" + | "PublicKey"; + +type RawNonLiteralIdlType = + | RawIdlTypeDefined + | RawIdlTypeOption + | RawIdlTypeVec + | RawIdlTypeArray; + +type RawIdlType = RawLiteralIdlType | RawNonLiteralIdlType; + +export type LiteralIdlType = | "bool" | "u8" | "i8" @@ -115,22 +233,39 @@ export type IdlType = | "i128" | "bytes" | "string" - | "publicKey" + | "publicKey"; + +export type NonLiteralIdlType = | IdlTypeDefined | IdlTypeOption | IdlTypeCOption | IdlTypeVec | IdlTypeArray; +export type IdlType = LiteralIdlType | NonLiteralIdlType; + +// User defined type. +type RawIdlTypeDefined = { + Defined: string; +}; + // User defined type. export type IdlTypeDefined = { defined: string; }; +type RawIdlTypeOption = { + Option: RawIdlType; +}; + export type IdlTypeOption = { option: IdlType; }; +type RawIdlTypeVec = { + Vec: RawIdlType; +}; + export type IdlTypeCOption = { coption: IdlType; }; @@ -139,19 +274,34 @@ export type IdlTypeVec = { vec: IdlType; }; +type RawIdlTypeArray = { + Array: [idlType: RawIdlType, size: number]; +}; + export type IdlTypeArray = { array: [idlType: IdlType, size: number]; }; +type RawIdlEnumVariant = { + name: string; + fields?: RawIdlEnumFields; +}; + export type IdlEnumVariant = { name: string; fields?: IdlEnumFields; }; +type RawIdlEnumFields = RawIdlEnumFieldsNamed | RawIdlEnumFieldsTuple; + type IdlEnumFields = IdlEnumFieldsNamed | IdlEnumFieldsTuple; +type RawIdlEnumFieldsNamed = RawIdlField[]; + type IdlEnumFieldsNamed = IdlField[]; +type RawIdlEnumFieldsTuple = RawIdlType[]; + type IdlEnumFieldsTuple = IdlType[]; export type IdlErrorCode = { @@ -160,6 +310,270 @@ export type IdlErrorCode = { msg?: string; }; +function camelCaseAccount(acc: RawIdlAccount): IdlAccount { + return { + name: camelCase(acc.name), + isMut: acc.is_mut, + isSigner: acc.is_signer, + }; +} + +function isAccounts(account: RawIdlAccountItem): account is RawIdlAccounts { + return (account as RawIdlAccounts).accounts !== undefined; +} + +function camelCaseAccountItem(account: RawIdlAccount): IdlAccount; +function camelCaseAccountItem(account: RawIdlAccounts): IdlAccounts; +function camelCaseAccountItem(account: RawIdlAccountItem): IdlAccountItem { + if (isAccounts(account)) { + return { + name: camelCase(account.name), + accounts: camelCaseAccountItems(account.accounts), + }; + } + return camelCaseAccount(account); +} + +function camelCaseAccountItems( + accounts: RawIdlAccountItem[] +): IdlAccountItem[] { + return accounts.map(camelCaseAccountItem); +} + +function isLiteral(type: RawIdlType): type is RawLiteralIdlType { + return typeof (type as RawLiteralIdlType) === "string"; +} + +function isIdlTypeDefined( + type: RawNonLiteralIdlType +): type is RawIdlTypeDefined { + return (type as RawIdlTypeDefined).Defined !== undefined; +} + +function isIdlTypeOption(type: RawNonLiteralIdlType): type is RawIdlTypeOption { + return (type as RawIdlTypeOption).Option !== undefined; +} + +function isIdlTypeVec(type: RawNonLiteralIdlType): type is RawIdlTypeVec { + return (type as RawIdlTypeVec).Vec !== undefined; +} + +function camelCaseIdlTypeDefined(type: RawIdlTypeDefined): IdlTypeDefined { + return { defined: type.Defined }; +} + +function camelCaseIdlTypeOption(type: RawIdlTypeOption): IdlTypeOption { + return { option: camelCaseIdlType(type.Option) }; +} + +function camelCaseIdlTypeVec(type: RawIdlTypeVec): IdlTypeVec { + return { vec: camelCaseIdlType(type.Vec) }; +} + +function camelCaseIdlTypeArray(type: RawIdlTypeArray): IdlTypeArray { + return { array: [camelCaseIdlType(type.Array[0]), type.Array[1]] }; +} + +function camelCaseIdlType(type: RawIdlType): IdlType { + if (isLiteral(type)) { + return camelCase(type) as LiteralIdlType; + } + if (isIdlTypeDefined(type)) { + return camelCaseIdlTypeDefined(type); + } + if (isIdlTypeOption(type)) { + return camelCaseIdlTypeOption(type); + } + if (isIdlTypeVec(type)) { + return camelCaseIdlTypeVec(type); + } + return camelCaseIdlTypeArray(type); +} + +function camelCaseIdlField(field: RawIdlField): IdlField { + return { + name: camelCase(field.name), + type: camelCaseIdlType(field.type), + }; +} + +function camelCaseIdlFields(args: RawIdlField[]): IdlField[] { + return args.map(camelCaseIdlField); +} + +function camelCaseIdlInstruction( + rawInstruction: RawIdlInstruction +): IdlInstruction { + return { + name: camelCase(rawInstruction.name), + accounts: camelCaseAccountItems(rawInstruction.accounts), + args: camelCaseIdlFields(rawInstruction.args), + }; +} + +function camelCaseIdlInstructions( + rawIdlInstructions: RawIdlInstruction[] +): IdlInstruction[] { + return rawIdlInstructions.map(camelCaseIdlInstruction); +} + +function isRawIdlTypeDefTyEnum( + rawIdlTypeDefTy: RawIdlTypeDefTy +): rawIdlTypeDefTy is RawIdlTypeDefTyEnum { + return rawIdlTypeDefTy.kind === "enum"; +} + +function isRawIdlEnumFieldsNamed( + fields: RawIdlEnumFields +): fields is RawIdlEnumFieldsNamed { + return (fields as RawIdlEnumFieldsNamed)[0].name !== undefined; +} + +function camelCaseIdlEnumFieldsNamed( + fields: RawIdlEnumFieldsNamed +): IdlEnumFieldsNamed { + return fields.map(camelCaseIdlField); +} + +function camelCaseIdlEnumFieldsTuple( + fields: RawIdlEnumFieldsTuple +): IdlEnumFieldsTuple { + return fields.map(camelCaseIdlType); +} + +function camelCaseIdlEnumFields( + fields?: RawIdlEnumFields +): IdlEnumFields | undefined { + if (fields === undefined) { + return undefined; + } + if (isRawIdlEnumFieldsNamed(fields)) { + return camelCaseIdlEnumFieldsNamed(fields); + } + return camelCaseIdlEnumFieldsTuple(fields); +} + +function camelCaseIdlEnumVariant(variant: RawIdlEnumVariant): IdlEnumVariant { + return { + name: camelCase(variant.name), + fields: camelCaseIdlEnumFields(variant.fields), + }; +} + +function camelCaseIdlEnumVariants( + variants: RawIdlEnumVariant[] +): IdlEnumVariant[] { + return variants.map(camelCaseIdlEnumVariant); +} + +function camelCaseIdlTypeDefStruct( + fields: RawIdlTypeDefStruct +): IdlTypeDefStruct { + return fields.map(camelCaseIdlField); +} + +function camelCaseIdlTypeDefTy(rawIdlTypeDefTy: RawIdlTypeDefTy): IdlTypeDefTy { + if (isRawIdlTypeDefTyEnum(rawIdlTypeDefTy)) { + return { + kind: "enum", + variants: camelCaseIdlEnumVariants(rawIdlTypeDefTy.variants), + }; + } + return { + kind: "struct", + fields: camelCaseIdlTypeDefStruct(rawIdlTypeDefTy.fields), + }; +} + +function camelCaseIdlTypeDef(rawIdlTypeDef: RawIdlTypeDef): IdlTypeDef { + return { + name: rawIdlTypeDef.name, + type: camelCaseIdlTypeDefTy(rawIdlTypeDef.type), + }; +} + +function camelCaseState(rawIdlState?: RawIdlState): IdlState | undefined { + if (rawIdlState === undefined) { + return undefined; + } + return { + struct: camelCaseIdlTypeDef(rawIdlState.struct), + methods: camelCaseIdlInstructions(rawIdlState.methods), + }; +} + +function camelCaseMaybeIdlTypeDefs( + typeDefs?: RawIdlTypeDef[] +): IdlTypeDef[] | undefined { + if (typeDefs === undefined) { + return undefined; + } + return typeDefs.map(camelCaseIdlTypeDef); +} + +function camelCaseIdlEventField(field: RawIdlEventField): IdlEventField { + return { + name: camelCase(field.name), + type: camelCaseIdlType(field.type), + index: field.index, + }; +} + +function camelCaseIdlEventFields(fields: RawIdlEventField[]): IdlEventField[] { + return fields.map(camelCaseIdlEventField); +} + +function camelCaseEvent(event: RawIdlEvent): IdlEvent { + return { + name: event.name, + fields: camelCaseIdlEventFields(event.fields), + }; +} + +function camelCaseEvents(events?: RawIdlEvent[]): IdlEvent[] | undefined { + if (events === undefined) { + return undefined; + } + return events.map(camelCaseEvent); +} + +function camelCaseConstant(constant: RawIdlConstant): IdlConstant { + return { + name: constant.name, + type: camelCaseIdlType(constant.type), + value: constant.value, + }; +} + +function camelCaseConstants( + constants?: RawIdlConstant[] +): IdlConstant[] | undefined { + if (constants === undefined) { + return undefined; + } + return constants.map(camelCaseConstant); +} + +export function isCamelized(idl: Idl | RawIdl): idl is Idl { + return (idl as Idl).camelized === true; +} + +export function camelCaseIdl(rawIdl: RawIdl): IDL { + return { + version: rawIdl.version, + name: camelCase(rawIdl.name), + instructions: camelCaseIdlInstructions(rawIdl.instructions), + state: camelCaseState(rawIdl.state), + accounts: camelCaseMaybeIdlTypeDefs(rawIdl.accounts), + types: camelCaseMaybeIdlTypeDefs(rawIdl.types), + events: camelCaseEvents(rawIdl.events), + errors: rawIdl.errors, + constants: camelCaseConstants(rawIdl.constants), + camelized: true, + metadata: rawIdl.metadata, + } as IDL; +} + // Deterministic IDL address as a function of the program id. export async function idlAddress(programId: PublicKey): Promise { const base = (await PublicKey.findProgramAddress([], programId))[0]; diff --git a/ts/src/program/index.ts b/ts/src/program/index.ts index ec2dff5675..ed8072ba0f 100644 --- a/ts/src/program/index.ts +++ b/ts/src/program/index.ts @@ -1,7 +1,14 @@ import { inflate } from "pako"; import { PublicKey } from "@solana/web3.js"; +import { + Idl, + idlAddress, + decodeIdlAccount, + camelCaseIdl, + RawIdl, + isCamelized, +} from "../idl.js"; import Provider, { getProvider } from "../provider.js"; -import { Idl, idlAddress, decodeIdlAccount } from "../idl.js"; import { Coder, BorshCoder } from "../coder/index.js"; import NamespaceFactory, { RpcNamespace, @@ -274,12 +281,14 @@ export class Program { if (!provider) { provider = getProvider(); } - + const camelizedIdl = isCamelized(idl) + ? (idl as IDL) + : camelCaseIdl(idl as RawIdl); // Fields. - this._idl = idl; + this._idl = camelizedIdl; this._provider = provider; this._programId = programId; - this._coder = coder ?? new BorshCoder(idl); + this._coder = coder ?? new BorshCoder(camelizedIdl); this._events = new EventManager(this._programId, provider, this._coder); // Dynamic namespaces. @@ -292,7 +301,7 @@ export class Program { methods, state, views, - ] = NamespaceFactory.build(idl, this._coder, programId, provider); + ] = NamespaceFactory.build(camelizedIdl, this._coder, programId, provider); this.rpc = rpc; this.instruction = instruction; this.transaction = transaction; @@ -322,7 +331,6 @@ export class Program { if (!idl) { throw new Error(`IDL not found for program: ${address.toString()}`); } - return new Program(idl, programId, provider); } @@ -350,7 +358,8 @@ export class Program { // Chop off account discriminator. let idlAccount = decodeIdlAccount(accountInfo.data.slice(8)); const inflatedIdl = inflate(idlAccount.data); - return JSON.parse(utf8.decode(inflatedIdl)); + const rawIdl = JSON.parse(utf8.decode(inflatedIdl)); + return camelCaseIdl(rawIdl); } /**