Add support for idl enum tuples, enchance types (#2185)

This commit is contained in:
Totoro 2022-10-18 17:13:23 +03:00 committed by GitHub
parent 0f10a99995
commit 8e66d5bb5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 199 additions and 31 deletions

View File

@ -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,
}

View File

@ -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(())

View File

@ -5,6 +5,8 @@ import {
IdlAccounts,
AnchorError,
Wallet,
IdlTypes,
IdlEvents,
} from "@project-serum/anchor";
import {
PublicKey,
@ -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 () => {

View File

@ -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);
});

View File

@ -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";

View File

@ -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,
@ -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;
type FieldsOfType<I extends IdlTypeDef> = NonNullable<
I["type"] extends IdlTypeDefTyStruct
? I["type"]["fields"]
: I["type"] extends IdlTypeDefTyEnum
? I["type"]["variants"][number]["fields"]
: any[]
>[number];
/**
* decode single enum.field
*/
declare type DecodeEnumField<F, Defined> = F extends IdlType
? DecodeType<F, Defined>
: never;
export type TypeDef<I extends IdlTypeDef, Defined> = {
[F in FieldsOfType<I> as F["name"]]: DecodeType<F["type"], Defined>;
/**
* 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>;
/**
* 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
>;
};
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"]>,