From 454f1dd044b73bdfb7eaea4a093bb1f61d69d00c Mon Sep 17 00:00:00 2001 From: acheron <98934430+acheroncrypto@users.noreply.github.com> Date: Sun, 13 Aug 2023 16:50:28 +0200 Subject: [PATCH] ts: Add support for unnamed(tuple) enum in accounts (#2601) --- CHANGELOG.md | 2 + .../programs/misc-optional/src/account.rs | 22 +++++++++ .../programs/misc-optional/src/context.rs | 8 +++ .../misc/programs/misc-optional/src/event.rs | 18 +------ tests/misc/programs/misc-optional/src/lib.rs | 36 ++++++++++---- tests/misc/programs/misc/src/account.rs | 22 +++++++++ tests/misc/programs/misc/src/context.rs | 11 ++++- tests/misc/programs/misc/src/event.rs | 22 ++------- tests/misc/programs/misc/src/lib.rs | 35 +++++++++---- tests/misc/tests/misc/misc.ts | 49 +++++++++++++++++++ ts/packages/anchor/src/coder/common.ts | 41 ++++++++-------- 11 files changed, 192 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5bb5b77e7..316b47608d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ The minor version will be incremented upon a breaking change and the patch versi - avm: Add support for the `.anchorversion` file to facilitate switching between different versions of the `anchor-cli` ([#2553](https://github.com/coral-xyz/anchor/pull/2553)). - ts: Add ability to access workspace programs independent of the casing used, e.g. `anchor.workspace.myProgram`, `anchor.workspace.MyProgram`... ([#2579](https://github.com/coral-xyz/anchor/pull/2579)). - spl: Export `mpl-token-metadata` crate ([#2583](https://github.com/coral-xyz/anchor/pull/2583)). +- spl: Add `TokenRecordAccount` for pNFTs ([#2597](https://github.com/coral-xyz/anchor/pull/2597)). +- ts: Add support for unnamed(tuple) enum in accounts([#2601](https://github.com/coral-xyz/anchor/pull/2601)). ### Fixes diff --git a/tests/misc/programs/misc-optional/src/account.rs b/tests/misc/programs/misc-optional/src/account.rs index 893dac256b..d67de200c8 100644 --- a/tests/misc/programs/misc-optional/src/account.rs +++ b/tests/misc/programs/misc-optional/src/account.rs @@ -36,6 +36,12 @@ pub struct DataI16 { } size!(DataI16, 2); +#[account] +pub struct DataEnum { + pub data: TestEnum, // 1 + 16 +} +size!(DataEnum, 17); + #[account(zero_copy)] pub struct DataZeroCopy { pub data: u16, // 2 @@ -73,3 +79,19 @@ pub struct DataConstCastArraySize { pub struct DataMultidimensionalArrayConstSizes { pub data: [[u8; MAX_SIZE_U8 as usize]; MAX_SIZE], } + +#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] +pub enum TestEnum { + First, + Second { x: u64, y: u64 }, + TupleTest(u8, u8, u16, u16), + TupleStructTest(TestStruct), +} + +#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] +pub struct TestStruct { + pub data1: u8, + pub data2: u16, + pub data3: u32, + pub data4: u64, +} diff --git a/tests/misc/programs/misc-optional/src/context.rs b/tests/misc/programs/misc-optional/src/context.rs index 04ec786f49..85c3d7f506 100644 --- a/tests/misc/programs/misc-optional/src/context.rs +++ b/tests/misc/programs/misc-optional/src/context.rs @@ -208,6 +208,14 @@ pub struct TestI16<'info> { #[derive(Accounts)] pub struct TestSimulate {} +#[derive(Accounts)] +pub struct TestAccountEnum<'info> { + #[account(init, payer = payer.as_ref().unwrap(), space = 8+ DataEnum::LEN )] + pub data: Option>, + pub payer: Option>, + pub system_program: Option>, +} + #[derive(Accounts)] pub struct TestI8<'info> { #[account(zero)] diff --git a/tests/misc/programs/misc-optional/src/event.rs b/tests/misc/programs/misc-optional/src/event.rs index 7a41c33786..c425e3cade 100644 --- a/tests/misc/programs/misc-optional/src/event.rs +++ b/tests/misc/programs/misc-optional/src/event.rs @@ -1,5 +1,7 @@ use anchor_lang::prelude::*; +use crate::account::*; + pub const MAX_EVENT_SIZE: usize = 10; pub const MAX_EVENT_SIZE_U8: u8 = 11; @@ -33,22 +35,6 @@ 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, diff --git a/tests/misc/programs/misc-optional/src/lib.rs b/tests/misc/programs/misc-optional/src/lib.rs index f8b2848364..8a847ac6cf 100644 --- a/tests/misc/programs/misc-optional/src/lib.rs +++ b/tests/misc/programs/misc-optional/src/lib.rs @@ -1,7 +1,7 @@ //! Misc optional example is a catchall program for testing unrelated features. //! It's not too instructive/coherent by itself, so please see other examples. -use account::MAX_SIZE; +use account::*; use anchor_lang::prelude::*; use context::*; use event::*; @@ -62,11 +62,16 @@ pub mod misc_optional { Ok(()) } - pub fn test_input_enum(ctx: Context, data: TestEnum) -> Result<()> { + pub fn test_input_enum(_ctx: Context, data: TestEnum) -> Result<()> { emit!(E7 { data: data }); Ok(()) } + pub fn test_account_enum(ctx: Context, data: TestEnum) -> Result<()> { + ctx.accounts.data.as_mut().unwrap().data = data; + Ok(()) + } + pub fn test_i8(ctx: Context, data: i8) -> Result<()> { ctx.accounts.data.as_mut().unwrap().data = data; Ok(()) @@ -171,7 +176,9 @@ pub mod misc_optional { Ok(()) } - pub fn test_init_mint_with_token_program(_ctx: Context) -> Result<()> { + pub fn test_init_mint_with_token_program( + _ctx: Context, + ) -> Result<()> { Ok(()) } @@ -182,11 +189,12 @@ pub mod misc_optional { Ok(()) } - pub fn test_init_token_with_token_program(_ctx: Context) -> Result<()> { + pub fn test_init_token_with_token_program( + _ctx: Context, + ) -> Result<()> { Ok(()) } - pub fn test_composite_payer(ctx: Context) -> Result<()> { ctx.accounts.composite.data.as_mut().unwrap().data = 1; ctx.accounts.data.as_mut().unwrap().udata = 2; @@ -201,7 +209,9 @@ pub mod misc_optional { Ok(()) } - pub fn test_init_associated_token_with_token_program(ctx: Context) -> Result<()> { + pub fn test_init_associated_token_with_token_program( + _ctx: Context, + ) -> Result<()> { Ok(()) } @@ -261,7 +271,9 @@ pub mod misc_optional { Ok(()) } - pub fn test_init_token_if_needed_with_token_program(_ctx: Context) -> Result<()> { + pub fn test_init_token_if_needed_with_token_program( + _ctx: Context, + ) -> Result<()> { Ok(()) } @@ -277,7 +289,7 @@ pub mod misc_optional { Ok(()) } - pub fn init_with_space(_ctx: Context, data: u16) -> Result<()> { + pub fn init_with_space(_ctx: Context, _data: u16) -> Result<()> { Ok(()) } @@ -357,7 +369,9 @@ pub mod misc_optional { Ok(()) } - pub fn test_only_token_program_constraint(_ctx: Context) -> Result<()> { + pub fn test_only_token_program_constraint( + _ctx: Context, + ) -> Result<()> { Ok(()) } @@ -401,7 +415,9 @@ pub mod misc_optional { Ok(()) } - pub fn test_associated_token_with_token_program_constraint(_ctx: Context) -> Result<()> { + pub fn test_associated_token_with_token_program_constraint( + _ctx: Context, + ) -> Result<()> { Ok(()) } } diff --git a/tests/misc/programs/misc/src/account.rs b/tests/misc/programs/misc/src/account.rs index c2966b5b44..d4d38f6aa7 100644 --- a/tests/misc/programs/misc/src/account.rs +++ b/tests/misc/programs/misc/src/account.rs @@ -36,6 +36,12 @@ pub struct DataI16 { } size!(DataI16, 2); +#[account] +pub struct DataEnum { + pub data: TestEnum, // 1 + 16 +} +size!(DataEnum, 17); + #[account(zero_copy)] pub struct DataZeroCopy { pub data: u16, // 2 @@ -94,3 +100,19 @@ pub enum CoolEnum { some_slot: 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), +} + +#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] +pub struct TestStruct { + pub data1: u8, + pub data2: u16, + pub data3: u32, + pub data4: u64, +} diff --git a/tests/misc/programs/misc/src/context.rs b/tests/misc/programs/misc/src/context.rs index 3ef62f3bbf..9e1ad323ea 100644 --- a/tests/misc/programs/misc/src/context.rs +++ b/tests/misc/programs/misc/src/context.rs @@ -215,6 +215,15 @@ pub struct TestI16<'info> { #[derive(Accounts)] pub struct TestSimulate {} +#[derive(Accounts)] +pub struct TestAccountEnum<'info> { + #[account(init, payer = payer, space = 8 + DataEnum::LEN)] + pub data: Account<'info, DataEnum>, + #[account(mut)] + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, +} + #[derive(Accounts)] pub struct TestI8<'info> { #[account(zero)] @@ -767,4 +776,4 @@ pub struct TestUsedIdentifiers<'info> { )] /// CHECK: ignore pub test4: AccountInfo<'info>, -} \ No newline at end of file +} diff --git a/tests/misc/programs/misc/src/event.rs b/tests/misc/programs/misc/src/event.rs index 31fcbd31d4..c425e3cade 100644 --- a/tests/misc/programs/misc/src/event.rs +++ b/tests/misc/programs/misc/src/event.rs @@ -1,5 +1,7 @@ use anchor_lang::prelude::*; +use crate::account::*; + pub const MAX_EVENT_SIZE: usize = 10; pub const MAX_EVENT_SIZE_U8: u8 = 11; @@ -33,23 +35,7 @@ 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] +#[event] pub struct E7 { pub data: TestEnum, -} \ No newline at end of file +} diff --git a/tests/misc/programs/misc/src/lib.rs b/tests/misc/programs/misc/src/lib.rs index b1e88aeb1f..5174b21c4f 100644 --- a/tests/misc/programs/misc/src/lib.rs +++ b/tests/misc/programs/misc/src/lib.rs @@ -1,7 +1,7 @@ //! Misc example is a catchall program for testing unrelated features. //! It's not too instructive/coherent by itself, so please see other examples. -use account::MAX_SIZE; +use account::*; use anchor_lang::prelude::*; use context::*; use event::*; @@ -71,6 +71,11 @@ pub mod misc { Ok(()) } + pub fn test_account_enum(ctx: Context, data: TestEnum) -> Result<()> { + ctx.accounts.data.data = data; + Ok(()) + } + pub fn test_i8(ctx: Context, data: i8) -> Result<()> { ctx.accounts.data.data = data; Ok(()) @@ -175,7 +180,9 @@ pub mod misc { Ok(()) } - pub fn test_init_mint_with_token_program(_ctx: Context) -> Result<()> { + pub fn test_init_mint_with_token_program( + _ctx: Context, + ) -> Result<()> { Ok(()) } @@ -184,7 +191,9 @@ pub mod misc { Ok(()) } - pub fn test_init_token_with_token_program(_ctx: Context) -> Result<()> { + pub fn test_init_token_with_token_program( + _ctx: Context, + ) -> Result<()> { Ok(()) } @@ -200,7 +209,9 @@ pub mod misc { Ok(()) } - pub fn test_init_associated_token_with_token_program(_ctx: Context) -> Result<()> { + pub fn test_init_associated_token_with_token_program( + _ctx: Context, + ) -> Result<()> { Ok(()) } @@ -250,7 +261,7 @@ pub mod misc { } pub fn test_init_mint_if_needed_with_token_program( - _ctx: Context + _ctx: Context, ) -> Result<()> { Ok(()) } @@ -259,7 +270,9 @@ pub mod misc { Ok(()) } - pub fn test_init_token_if_needed_with_token_program(_ctx: Context) -> Result<()> { + pub fn test_init_token_if_needed_with_token_program( + _ctx: Context, + ) -> Result<()> { Ok(()) } @@ -345,7 +358,9 @@ pub mod misc { Ok(()) } - pub fn test_only_token_program_constraint(_ctx: Context) -> Result<()> { + pub fn test_only_token_program_constraint( + _ctx: Context, + ) -> Result<()> { Ok(()) } @@ -389,7 +404,9 @@ pub mod misc { Ok(()) } - pub fn test_associated_token_with_token_program_constraint(_ctx: Context) -> Result<()> { + pub fn test_associated_token_with_token_program_constraint( + _ctx: Context, + ) -> Result<()> { Ok(()) } @@ -399,7 +416,7 @@ pub mod misc { program_id: u8, accounts: u8, ix_data: u8, - remaining_accounts: u8 + remaining_accounts: u8, ) -> Result<()> { Ok(()) } diff --git a/tests/misc/tests/misc/misc.ts b/tests/misc/tests/misc/misc.ts index 1a526d6ed9..fd450588bf 100644 --- a/tests/misc/tests/misc/misc.ts +++ b/tests/misc/tests/misc/misc.ts @@ -336,6 +336,55 @@ const miscTest = ( assert.strictEqual(event4.data.tupleTest[3], 4); }); + it("Can use enum in accounts", async () => { + const testAccountEnum = async ( + ...args: Parameters + ) => { + const dataKp = anchor.web3.Keypair.generate(); + const txHash = await program.methods + .testAccountEnum(...(args as any)) + .accounts({ + data: dataKp.publicKey, + payer: program.provider.publicKey, + }) + .signers([dataKp]) + .rpc(); + await program.provider.connection.confirmTransaction(txHash); + return await program.account.dataEnum.fetch(dataKp.publicKey); + }; + + // Unit + const unit = await testAccountEnum({ first: {} }); + assert.deepEqual(unit.data.first, {}); + + // Named + const named = await testAccountEnum({ + second: { x: new BN(1), y: new BN(2) }, + }); + assert.isTrue(new BN(1).eq(named.data.second.x)); + assert.isTrue(new BN(2).eq(named.data.second.y)); + + // Unnamed + const unnamed = await testAccountEnum({ tupleTest: [1, 2, 3, 4] }); + assert.strictEqual(unnamed.data.tupleTest[0], 1); + assert.strictEqual(unnamed.data.tupleTest[1], 2); + assert.strictEqual(unnamed.data.tupleTest[2], 3); + assert.strictEqual(unnamed.data.tupleTest[3], 4); + + // Unnamed struct + const unnamedStruct = await testAccountEnum({ + tupleStructTest: [ + { data1: 1, data2: 11, data3: 111, data4: new BN(1111) }, + ], + }); + assert.strictEqual(unnamedStruct.data.tupleStructTest[0].data1, 1); + assert.strictEqual(unnamedStruct.data.tupleStructTest[0].data2, 11); + assert.strictEqual(unnamedStruct.data.tupleStructTest[0].data3, 111); + assert.isTrue( + unnamedStruct.data.tupleStructTest[0].data4.eq(new BN(1111)) + ); + }); + let dataI8; it("Can use i8 in the idl", async () => { diff --git a/ts/packages/anchor/src/coder/common.ts b/ts/packages/anchor/src/coder/common.ts index da31dc58e8..202d10a65e 100644 --- a/ts/packages/anchor/src/coder/common.ts +++ b/ts/packages/anchor/src/coder/common.ts @@ -1,31 +1,32 @@ -import { Idl, IdlField, IdlTypeDef, IdlEnumVariant, IdlType } from "../idl.js"; +import { Idl, IdlField, IdlTypeDef, IdlType } from "../idl.js"; import { IdlError } from "../error.js"; -export function accountSize(idl: Idl, idlAccount: IdlTypeDef): number { +export function accountSize(idl: Idl, idlAccount: IdlTypeDef) { if (idlAccount.type.kind === "enum") { - let variantSizes = idlAccount.type.variants.map( - (variant: IdlEnumVariant) => { - if (variant.fields === undefined) { - return 0; - } - return variant.fields - .map((f: IdlField | IdlType) => { - if (!(typeof f === "object" && "name" in f)) { - throw new Error("Tuple enum variants not yet implemented."); - } - return typeSize(idl, f.type); - }) - .reduce((a: number, b: number) => a + b); + const variantSizes = idlAccount.type.variants.map((variant) => { + if (!variant.fields) { + return 0; } - ); + + return variant.fields + .map((f: IdlField | IdlType) => { + // Unnamed enum variant + if (!(typeof f === "object" && "name" in f)) { + return typeSize(idl, f); + } + + // Named enum variant + return typeSize(idl, f.type); + }) + .reduce((acc, size) => acc + size, 0); + }); + return Math.max(...variantSizes) + 1; } - if (idlAccount.type.fields === undefined) { - return 0; - } + return idlAccount.type.fields .map((f) => typeSize(idl, f.type)) - .reduce((a, b) => a + b, 0); + .reduce((acc, size) => acc + size, 0); } // Returns the size of the type in bytes. For variable length types, just return