From 2529b06c02b0b40cf17e2017f3f8fd90b935826e Mon Sep 17 00:00:00 2001 From: Tom Linton Date: Sat, 12 Mar 2022 12:05:19 +1300 Subject: [PATCH] lang: Handle const array size casting and add const array size support for events (#1485) --- CHANGELOG.md | 1 + lang/syn/src/idl/file.rs | 74 +++++++++++++++++-------- tests/misc/programs/misc/src/account.rs | 12 ++++ tests/misc/programs/misc/src/context.rs | 6 ++ tests/misc/programs/misc/src/event.rs | 13 +++++ tests/misc/programs/misc/src/lib.rs | 14 +++++ tests/misc/tests/misc.js | 34 +++++++++++- 7 files changed, 130 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46a13279..381833fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ incremented for features. ### Fixes * cli: Fix rust template ([#1488](https://github.com/project-serum/anchor/pull/1488)). +* lang: Handle array sizes with variable sizes in events and array size casting in IDL parsing ([#1485](https://github.com/project-serum/anchor/pull/1485)) ## [0.22.0] - 2022-02-20 diff --git a/lang/syn/src/idl/file.rs b/lang/syn/src/idl/file.rs index 2f14100e..f221366a 100644 --- a/lang/syn/src/idl/file.rs +++ b/lang/syn/src/idl/file.rs @@ -194,7 +194,7 @@ pub fn parse( }; IdlEventField { name: f.ident.clone().unwrap().to_string().to_mixed_case(), - ty: parser::tts_to_string(&f.ty).parse().unwrap(), + ty: to_idl_type(&ctx, f), index, } }) @@ -409,16 +409,9 @@ fn parse_ty_defs(ctx: &CrateContext) -> Result> { .named .iter() .map(|f: &syn::Field| { - let mut tts = proc_macro2::TokenStream::new(); - f.ty.to_tokens(&mut tts); - // Handle array sizes that are constants - let mut tts_string = tts.to_string(); - if tts_string.starts_with('[') { - tts_string = resolve_variable_array_length(ctx, tts_string); - } Ok(IdlField { name: f.ident.as_ref().unwrap().to_string().to_mixed_case(), - ty: tts_string.parse()?, + ty: to_idl_type(ctx, f), }) }) .collect::>>(), @@ -442,7 +435,7 @@ fn parse_ty_defs(ctx: &CrateContext) -> Result> { syn::Fields::Unit => None, syn::Fields::Unnamed(fields) => { let fields: Vec = - fields.unnamed.iter().map(to_idl_type).collect(); + fields.unnamed.iter().map(|f| to_idl_type(ctx, f)).collect(); Some(EnumFields::Tuple(fields)) } syn::Fields::Named(fields) => { @@ -451,7 +444,7 @@ fn parse_ty_defs(ctx: &CrateContext) -> Result> { .iter() .map(|f: &syn::Field| { let name = f.ident.as_ref().unwrap().to_string(); - let ty = to_idl_type(f); + let ty = to_idl_type(ctx, f); IdlField { name, ty } }) .collect(); @@ -470,11 +463,42 @@ fn parse_ty_defs(ctx: &CrateContext) -> Result> { } // Replace variable array lengths with values -fn resolve_variable_array_length(ctx: &CrateContext, tts_string: String) -> String { - for constant in ctx.consts() { - if constant.ty.to_token_stream().to_string() == "usize" - && tts_string.contains(&constant.ident.to_string()) - { +fn resolve_variable_array_lengths(ctx: &CrateContext, mut tts_string: String) -> String { + for constant in ctx.consts().filter(|c| match *c.ty { + // Filter to only those consts that are of type usize or could be cast to usize + syn::Type::Path(ref p) => { + let segment = p.path.segments.last().unwrap(); + matches!( + segment.ident.to_string().as_str(), + "usize" + | "u8" + | "u16" + | "u32" + | "u64" + | "u128" + | "isize" + | "i8" + | "i16" + | "i32" + | "i64" + | "i128" + ) + } + _ => false, + }) { + let mut check_string = tts_string.clone(); + // Strip whitespace to handle accidental double whitespaces + check_string.retain(|c| !c.is_whitespace()); + let size_string = format!("{}]", &constant.ident.to_string()); + let cast_size_string = format!("{}asusize]", &constant.ident.to_string()); + // Check for something to replace + let mut replacement_string = None; + if check_string.contains(cast_size_string.as_str()) { + replacement_string = Some(cast_size_string); + } else if check_string.contains(size_string.as_str()) { + replacement_string = Some(size_string); + } + if let Some(replacement_string) = replacement_string { // Check for the existence of consts existing elsewhere in the // crate which have the same name, are usize, and have a // different value. We can't know which was intended for the @@ -487,19 +511,23 @@ fn resolve_variable_array_length(ctx: &CrateContext, tts_string: String) -> Stri }) { panic!("Crate wide unique name required for array size const."); } - return tts_string.replace( - &constant.ident.to_string(), - &constant.expr.to_token_stream().to_string(), + // Replace the match, don't break because there might be multiple replacements to be + // made in the case of multidimensional arrays + tts_string = check_string.replace( + &replacement_string, + format!("{}]", &constant.expr.to_token_stream()).as_str(), ); } } tts_string } -fn to_idl_type(f: &syn::Field) -> IdlType { - let mut tts = proc_macro2::TokenStream::new(); - f.ty.to_tokens(&mut tts); - tts.to_string().parse().unwrap() +fn to_idl_type(ctx: &CrateContext, f: &syn::Field) -> IdlType { + let mut tts_string = parser::tts_to_string(&f.ty); + if tts_string.starts_with('[') { + tts_string = resolve_variable_array_lengths(ctx, tts_string); + } + tts_string.parse().unwrap() } fn idl_accounts( diff --git a/tests/misc/programs/misc/src/account.rs b/tests/misc/programs/misc/src/account.rs index 4a5fc13c..893dac25 100644 --- a/tests/misc/programs/misc/src/account.rs +++ b/tests/misc/programs/misc/src/account.rs @@ -9,6 +9,7 @@ macro_rules! size { } pub const MAX_SIZE: usize = 10; +pub const MAX_SIZE_U8: u8 = 11; #[account] pub struct Data { @@ -61,3 +62,14 @@ pub struct DataConstArraySize { pub data: [u8; MAX_SIZE], // 10 } size!(DataConstArraySize, MAX_SIZE); + +#[account] +pub struct DataConstCastArraySize { + pub data_one: [u8; MAX_SIZE as usize], + pub data_two: [u8; MAX_SIZE_U8 as usize], +} + +#[account] +pub struct DataMultidimensionalArrayConstSizes { + pub data: [[u8; MAX_SIZE_U8 as usize]; MAX_SIZE], +} diff --git a/tests/misc/programs/misc/src/context.rs b/tests/misc/programs/misc/src/context.rs index e34c6c71..51b14cc5 100644 --- a/tests/misc/programs/misc/src/context.rs +++ b/tests/misc/programs/misc/src/context.rs @@ -385,6 +385,12 @@ pub struct TestConstArraySize<'info> { pub data: Account<'info, DataConstArraySize>, } +#[derive(Accounts)] +pub struct TestMultidimensionalArrayConstSizes<'info> { + #[account(zero)] + pub data: Account<'info, DataMultidimensionalArrayConstSizes>, +} + #[derive(Accounts)] pub struct NoRentExempt<'info> { /// CHECK: diff --git a/tests/misc/programs/misc/src/event.rs b/tests/misc/programs/misc/src/event.rs index 84e5eb64..80b7dd8f 100644 --- a/tests/misc/programs/misc/src/event.rs +++ b/tests/misc/programs/misc/src/event.rs @@ -1,5 +1,8 @@ use anchor_lang::prelude::*; +pub const MAX_EVENT_SIZE: usize = 10; +pub const MAX_EVENT_SIZE_U8: u8 = 11; + #[event] pub struct E1 { pub data: u32, @@ -19,3 +22,13 @@ pub struct E3 { pub struct E4 { pub data: Pubkey, } + +#[event] +pub struct E5 { + pub data: [u8; MAX_EVENT_SIZE], +} + +#[event] +pub struct E6 { + pub data: [u8; MAX_EVENT_SIZE_U8 as usize], +} diff --git a/tests/misc/programs/misc/src/lib.rs b/tests/misc/programs/misc/src/lib.rs index acc9030a..25517282 100644 --- a/tests/misc/programs/misc/src/lib.rs +++ b/tests/misc/programs/misc/src/lib.rs @@ -82,6 +82,12 @@ pub mod misc { emit!(E1 { data }); emit!(E2 { data: 1234 }); emit!(E3 { data: 9 }); + emit!(E5 { + data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + }); + emit!(E6 { + data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + }); Ok(()) } @@ -247,6 +253,14 @@ pub mod misc { Ok(()) } + pub fn test_multidimensional_array_const_sizes( + ctx: Context, + data: [[u8; 11]; 10], + ) -> Result<()> { + ctx.accounts.data.data = data; + Ok(()) + } + pub fn test_no_rent_exempt(ctx: Context) -> Result<()> { Ok(()) } diff --git a/tests/misc/tests/misc.js b/tests/misc/tests/misc.js index 3e6cd982..4cd5d0e2 100644 --- a/tests/misc/tests/misc.js +++ b/tests/misc/tests/misc.js @@ -164,6 +164,16 @@ describe("misc", () => { assert.ok(resp.events[1].data.data === 1234); assert.ok(resp.events[2].name === "E3"); assert.ok(resp.events[2].data.data === 9); + assert.ok(resp.events[3].name === "E5"); + assert.deepStrictEqual( + resp.events[3].data.data, + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + ); + assert.ok(resp.events[4].name === "E6"); + assert.deepStrictEqual( + resp.events[4].data.data, + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + ); }); let dataI8; @@ -1432,7 +1442,7 @@ describe("misc", () => { it("Can use multidimensional array", async () => { const array2d = new Array(10).fill(new Array(10).fill(99)); const data = anchor.web3.Keypair.generate(); - const tx = await program.rpc.testMultidimensionalArray(array2d, { + await program.rpc.testMultidimensionalArray(array2d, { accounts: { data: data.publicKey, rent: anchor.web3.SYSVAR_RENT_PUBKEY, @@ -1448,6 +1458,28 @@ describe("misc", () => { assert.deepStrictEqual(dataAccount.data, array2d); }); + it("Can use multidimensional array with const sizes", async () => { + const array2d = new Array(10).fill(new Array(11).fill(22)); + const data = anchor.web3.Keypair.generate(); + await program.rpc.testMultidimensionalArrayConstSizes(array2d, { + accounts: { + data: data.publicKey, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + signers: [data], + instructions: [ + await program.account.dataMultidimensionalArrayConstSizes.createInstruction( + data + ), + ], + }); + const dataAccount = + await program.account.dataMultidimensionalArrayConstSizes.fetch( + data.publicKey + ); + assert.deepStrictEqual(dataAccount.data, array2d); + }); + it("allows non-rent exempt accounts", async () => { const data = anchor.web3.Keypair.generate(); await program.rpc.initializeNoRentExempt({