Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions tests/misc/programs/misc/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,24 @@ pub struct E5 {
pub struct E6 {
pub data: [u8; MAX_EVENT_SIZE_U8 as usize],
}

#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)]
pub struct TestStruct {
pub data1: u8,
pub data2: u16,
pub data3: u32,
pub data4: u64,
}

#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)]
pub enum TestEnum {
First,
Second {x: u64, y: u64},
TupleTest (u8, u8, u16, u16),
TupleStructTest (TestStruct),
}

#[event]
pub struct E7 {
pub data: TestEnum,
}
5 changes: 5 additions & 0 deletions tests/misc/programs/misc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ pub mod misc {
Ok(())
}

pub fn test_input_enum(ctx: Context<TestSimulate>, data: TestEnum) -> Result<()> {
emit!(E7 { data: data });
Ok(())
}

pub fn test_i8(ctx: Context<TestI8>, data: i8) -> Result<()> {
ctx.accounts.data.data = data;
Ok(())
Expand Down
37 changes: 37 additions & 0 deletions tests/misc/tests/misc/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
IdlAccounts,
AnchorError,
Wallet,
IdlTypes,
IdlEvents,
} from "@project-serum/anchor";
import {
PublicKey,
Expand Down Expand Up @@ -190,6 +192,41 @@ describe("misc", () => {
);
});

it("Can use enum in idl", async () => {
const resp1 = await program.methods.testInputEnum({ first: {} }).simulate();
const event1 = resp1.events[0].data as IdlEvents<Misc>["E7"];
assert.deepEqual(event1.data.first, {});

const resp2 = await program.methods
.testInputEnum({ second: { x: new BN(1), y: new BN(2) } })
.simulate();
const event2 = resp2.events[0].data as IdlEvents<Misc>["E7"];
assert.isTrue(new BN(1).eq(event2.data.second.x));
assert.isTrue(new BN(2).eq(event2.data.second.y));

const resp3 = await program.methods
.testInputEnum({
tupleStructTest: [
{ data1: 1, data2: 11, data3: 111, data4: new BN(1111) },
],
})
.simulate();
const event3 = resp3.events[0].data as IdlEvents<Misc>["E7"];
assert.strictEqual(event3.data.tupleStructTest[0].data1, 1);
assert.strictEqual(event3.data.tupleStructTest[0].data2, 11);
assert.strictEqual(event3.data.tupleStructTest[0].data3, 111);
assert.isTrue(event3.data.tupleStructTest[0].data4.eq(new BN(1111)));

const resp4 = await program.methods
.testInputEnum({ tupleTest: [1, 2, 3, 4] })
.simulate();
const event4 = resp4.events[0].data as IdlEvents<Misc>["E7"];
assert.strictEqual(event4.data.tupleTest[0], 1);
assert.strictEqual(event4.data.tupleTest[1], 2);
assert.strictEqual(event4.data.tupleTest[2], 3);
assert.strictEqual(event4.data.tupleTest[3], 4);
});

let dataI8;

it("Can use i8 in the idl", async () => {
Expand Down
23 changes: 14 additions & 9 deletions ts/packages/anchor/src/coder/borsh/idl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,16 +129,21 @@ export class IdlCoder {
if (variant.fields === undefined) {
return borsh.struct([], name);
}
const fieldLayouts = variant.fields.map((f: IdlField | IdlType) => {
if (!f.hasOwnProperty("name")) {
throw new Error("Tuple enum variants not yet implemented.");
const fieldLayouts = variant.fields.map(
(f: IdlField | IdlType, i: number) => {
if (!f.hasOwnProperty("name")) {
return IdlCoder.fieldLayout(
{ type: f as IdlType, name: i.toString() },
types
);
}
// this typescript conversion is ok
// because if f were of type IdlType
// (that does not have a name property)
// the check before would've errored
return IdlCoder.fieldLayout(f as IdlField, types);
}
// this typescript conversion is ok
// because if f were of type IdlType
// (that does not have a name property)
// the check before would've errored
return IdlCoder.fieldLayout(f as IdlField, types);
});
);
return borsh.struct(fieldLayouts, name);
});

Expand Down
2 changes: 1 addition & 1 deletion ts/packages/anchor/src/program/namespace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export { TransactionNamespace, TransactionFn } from "./transaction.js";
export { RpcNamespace, RpcFn } from "./rpc.js";
export { AccountNamespace, AccountClient, ProgramAccount } from "./account.js";
export { SimulateNamespace, SimulateFn } from "./simulate.js";
export { IdlAccounts, IdlTypes, DecodeType } from "./types.js";
export { IdlAccounts, IdlTypes, DecodeType, IdlEvents } from "./types.js";
export { MethodsBuilderFactory, MethodsNamespace } from "./methods";
export { ViewNamespace, ViewFn } from "./views";

Expand Down
142 changes: 121 additions & 21 deletions ts/packages/anchor/src/program/namespace/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { PublicKey } from "@solana/web3.js";
import BN from "bn.js";
import { Idl } from "../../";
import {
IdlEnumFields,
IdlEnumFieldsNamed,
IdlEnumFieldsTuple,
IdlField,
IdlInstruction,
IdlType,
Expand Down Expand Up @@ -140,37 +143,134 @@ type ArgsTuple<A extends IdlField[], Defined> = {
? DecodeType<A[K]["type"], Defined>
: unknown;
} & unknown[];
/**
* flat {a: number, b: {c: string}} into number | string
*/
type UnboxToUnion<T> = T extends (infer U)[]
? UnboxToUnion<U>
: T extends Record<string, never> // empty object, eg: named enum variant without fields
? "__empty_object__"
: T extends Record<string, infer V> // object with props, eg: struct
? UnboxToUnion<V>
: T;

/**
* decode single enum.field
*/
declare type DecodeEnumField<F, Defined> = F extends IdlType
? DecodeType<F, Defined>
: never;

/**
* decode enum variant: named or tuple
*/
declare type DecodeEnumFields<
F extends IdlEnumFields,
Defined
> = F extends IdlEnumFieldsNamed
? {
[F2 in F[number] as F2["name"]]: DecodeEnumField<F2["type"], Defined>;
}
: F extends IdlEnumFieldsTuple
? {
[F3 in keyof F as Exclude<F3, keyof unknown[]>]: DecodeEnumField<
F[F3],
Defined
>;
}
: Record<string, never>;

type FieldsOfType<I extends IdlTypeDef> = NonNullable<
I["type"] extends IdlTypeDefTyStruct
? I["type"]["fields"]
: I["type"] extends IdlTypeDefTyEnum
? I["type"]["variants"][number]["fields"]
: any[]
>[number];
/**
* Since TypeScript do not provide OneOf helper we can
* simply mark enum variants with +?
*/
declare type DecodeEnum<K extends IdlTypeDefTyEnum, Defined> = {
// X = IdlEnumVariant
[X in K["variants"][number] as Uncapitalize<X["name"]>]+?: DecodeEnumFields<
NonNullable<X["fields"]>,
Defined
>;
};

export type TypeDef<I extends IdlTypeDef, Defined> = {
[F in FieldsOfType<I> as F["name"]]: DecodeType<F["type"], Defined>;
type DecodeStruct<I extends IdlTypeDefTyStruct, Defined> = {
[F in I["fields"][number] as F["name"]]: DecodeType<F["type"], Defined>;
};

export type TypeDef<
I extends IdlTypeDef,
Defined
> = I["type"] extends IdlTypeDefTyEnum
? DecodeEnum<I["type"], Defined>
: I["type"] extends IdlTypeDefTyStruct
? DecodeStruct<I["type"], Defined>
: never;

type TypeDefDictionary<T extends IdlTypeDef[], Defined> = {
[K in T[number] as K["name"]]: TypeDef<K, Defined>;
};

type NestedTypeDefDictionary<T extends IdlTypeDef[]> = {
[Outer in T[number] as Outer["name"]]: TypeDef<
Outer,
{
[Inner in T[number] as Inner["name"]]: Inner extends Outer
? never
: TypeDef<Inner, Record<string, never>>;
}
>;
type DecodedHelper<T extends IdlTypeDef[], Defined> = {
[D in T[number] as D["name"]]: TypeDef<D, Defined>;
};

export type IdlTypes<T extends Idl> = NestedTypeDefDictionary<
NonNullable<T["types"]>
>;
type UnknownType = "__unknown_defined_type__";
/**
* empty "defined" object to produce UnknownType instead of never/unknown during idl types decoding
* */
type EmptyDefined = Record<UnknownType, never>;

type RecursiveDepth2<
T extends IdlTypeDef[],
Defined = EmptyDefined,
Decoded = DecodedHelper<T, Defined>
> = UnknownType extends UnboxToUnion<Decoded>
? RecursiveDepth3<T, DecodedHelper<T, Defined>>
: Decoded;

type RecursiveDepth3<
T extends IdlTypeDef[],
Defined = EmptyDefined,
Decoded = DecodedHelper<T, Defined>
> = UnknownType extends UnboxToUnion<Decoded>
? RecursiveDepth4<T, DecodedHelper<T, Defined>>
: Decoded;

type RecursiveDepth4<
T extends IdlTypeDef[],
Defined = EmptyDefined
> = DecodedHelper<T, Defined>;

/**
* typescript can't handle truly recursive type (RecursiveTypes instead of RecursiveDepth2).
* Hence we're doing "recursion" of depth=4 manually
* */
type RecursiveTypes<
T extends IdlTypeDef[],
Defined = EmptyDefined,
Decoded = DecodedHelper<T, Defined>
> =
// check if some of decoded types is Unknown (not decoded properly)
UnknownType extends UnboxToUnion<Decoded>
? RecursiveDepth2<T, DecodedHelper<T, Defined>>
: Decoded;

export type IdlTypes<T extends Idl> = RecursiveTypes<NonNullable<T["types"]>>;

type IdlEventType<
I extends Idl,
Event extends NonNullable<I["events"]>[number],
Defined
> = {
[F in Event["fields"][number] as F["name"]]: DecodeType<F["type"], Defined>;
};

export type IdlEvents<I extends Idl, Defined = IdlTypes<I>> = {
[E in NonNullable<I["events"]>[number] as E["name"]]: IdlEventType<
I,
E,
Defined
>;
};

export type IdlAccounts<T extends Idl> = TypeDefDictionary<
NonNullable<T["accounts"]>,
Expand Down