Add support for idl enum tuples, enchance types (#2185)
This commit is contained in:
parent
0f10a99995
commit
8e66d5bb5f
|
@ -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,
|
||||
}
|
|
@ -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(())
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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"]>,
|
||||
|
|
Loading…
Reference in New Issue