lang: Handle const array size casting and add const array size support for events (#1485)
This commit is contained in:
parent
69a2d44513
commit
2529b06c02
|
@ -35,6 +35,7 @@ incremented for features.
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
* cli: Fix rust template ([#1488](https://github.com/project-serum/anchor/pull/1488)).
|
* 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
|
## [0.22.0] - 2022-02-20
|
||||||
|
|
||||||
|
|
|
@ -194,7 +194,7 @@ pub fn parse(
|
||||||
};
|
};
|
||||||
IdlEventField {
|
IdlEventField {
|
||||||
name: f.ident.clone().unwrap().to_string().to_mixed_case(),
|
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,
|
index,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -409,16 +409,9 @@ fn parse_ty_defs(ctx: &CrateContext) -> Result<Vec<IdlTypeDefinition>> {
|
||||||
.named
|
.named
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f: &syn::Field| {
|
.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 {
|
Ok(IdlField {
|
||||||
name: f.ident.as_ref().unwrap().to_string().to_mixed_case(),
|
name: f.ident.as_ref().unwrap().to_string().to_mixed_case(),
|
||||||
ty: tts_string.parse()?,
|
ty: to_idl_type(ctx, f),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<IdlField>>>(),
|
.collect::<Result<Vec<IdlField>>>(),
|
||||||
|
@ -442,7 +435,7 @@ fn parse_ty_defs(ctx: &CrateContext) -> Result<Vec<IdlTypeDefinition>> {
|
||||||
syn::Fields::Unit => None,
|
syn::Fields::Unit => None,
|
||||||
syn::Fields::Unnamed(fields) => {
|
syn::Fields::Unnamed(fields) => {
|
||||||
let fields: Vec<IdlType> =
|
let fields: Vec<IdlType> =
|
||||||
fields.unnamed.iter().map(to_idl_type).collect();
|
fields.unnamed.iter().map(|f| to_idl_type(ctx, f)).collect();
|
||||||
Some(EnumFields::Tuple(fields))
|
Some(EnumFields::Tuple(fields))
|
||||||
}
|
}
|
||||||
syn::Fields::Named(fields) => {
|
syn::Fields::Named(fields) => {
|
||||||
|
@ -451,7 +444,7 @@ fn parse_ty_defs(ctx: &CrateContext) -> Result<Vec<IdlTypeDefinition>> {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f: &syn::Field| {
|
.map(|f: &syn::Field| {
|
||||||
let name = f.ident.as_ref().unwrap().to_string();
|
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 }
|
IdlField { name, ty }
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -470,11 +463,42 @@ fn parse_ty_defs(ctx: &CrateContext) -> Result<Vec<IdlTypeDefinition>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace variable array lengths with values
|
// Replace variable array lengths with values
|
||||||
fn resolve_variable_array_length(ctx: &CrateContext, tts_string: String) -> String {
|
fn resolve_variable_array_lengths(ctx: &CrateContext, mut tts_string: String) -> String {
|
||||||
for constant in ctx.consts() {
|
for constant in ctx.consts().filter(|c| match *c.ty {
|
||||||
if constant.ty.to_token_stream().to_string() == "usize"
|
// Filter to only those consts that are of type usize or could be cast to usize
|
||||||
&& tts_string.contains(&constant.ident.to_string())
|
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
|
// Check for the existence of consts existing elsewhere in the
|
||||||
// crate which have the same name, are usize, and have a
|
// crate which have the same name, are usize, and have a
|
||||||
// different value. We can't know which was intended for the
|
// 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.");
|
panic!("Crate wide unique name required for array size const.");
|
||||||
}
|
}
|
||||||
return tts_string.replace(
|
// Replace the match, don't break because there might be multiple replacements to be
|
||||||
&constant.ident.to_string(),
|
// made in the case of multidimensional arrays
|
||||||
&constant.expr.to_token_stream().to_string(),
|
tts_string = check_string.replace(
|
||||||
|
&replacement_string,
|
||||||
|
format!("{}]", &constant.expr.to_token_stream()).as_str(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tts_string
|
tts_string
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_idl_type(f: &syn::Field) -> IdlType {
|
fn to_idl_type(ctx: &CrateContext, f: &syn::Field) -> IdlType {
|
||||||
let mut tts = proc_macro2::TokenStream::new();
|
let mut tts_string = parser::tts_to_string(&f.ty);
|
||||||
f.ty.to_tokens(&mut tts);
|
if tts_string.starts_with('[') {
|
||||||
tts.to_string().parse().unwrap()
|
tts_string = resolve_variable_array_lengths(ctx, tts_string);
|
||||||
|
}
|
||||||
|
tts_string.parse().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn idl_accounts(
|
fn idl_accounts(
|
||||||
|
|
|
@ -9,6 +9,7 @@ macro_rules! size {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const MAX_SIZE: usize = 10;
|
pub const MAX_SIZE: usize = 10;
|
||||||
|
pub const MAX_SIZE_U8: u8 = 11;
|
||||||
|
|
||||||
#[account]
|
#[account]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
|
@ -61,3 +62,14 @@ pub struct DataConstArraySize {
|
||||||
pub data: [u8; MAX_SIZE], // 10
|
pub data: [u8; MAX_SIZE], // 10
|
||||||
}
|
}
|
||||||
size!(DataConstArraySize, MAX_SIZE);
|
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],
|
||||||
|
}
|
||||||
|
|
|
@ -385,6 +385,12 @@ pub struct TestConstArraySize<'info> {
|
||||||
pub data: Account<'info, DataConstArraySize>,
|
pub data: Account<'info, DataConstArraySize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct TestMultidimensionalArrayConstSizes<'info> {
|
||||||
|
#[account(zero)]
|
||||||
|
pub data: Account<'info, DataMultidimensionalArrayConstSizes>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct NoRentExempt<'info> {
|
pub struct NoRentExempt<'info> {
|
||||||
/// CHECK:
|
/// CHECK:
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
|
pub const MAX_EVENT_SIZE: usize = 10;
|
||||||
|
pub const MAX_EVENT_SIZE_U8: u8 = 11;
|
||||||
|
|
||||||
#[event]
|
#[event]
|
||||||
pub struct E1 {
|
pub struct E1 {
|
||||||
pub data: u32,
|
pub data: u32,
|
||||||
|
@ -19,3 +22,13 @@ pub struct E3 {
|
||||||
pub struct E4 {
|
pub struct E4 {
|
||||||
pub data: Pubkey,
|
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],
|
||||||
|
}
|
||||||
|
|
|
@ -82,6 +82,12 @@ pub mod misc {
|
||||||
emit!(E1 { data });
|
emit!(E1 { data });
|
||||||
emit!(E2 { data: 1234 });
|
emit!(E2 { data: 1234 });
|
||||||
emit!(E3 { data: 9 });
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,6 +253,14 @@ pub mod misc {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn test_multidimensional_array_const_sizes(
|
||||||
|
ctx: Context<TestMultidimensionalArrayConstSizes>,
|
||||||
|
data: [[u8; 11]; 10],
|
||||||
|
) -> Result<()> {
|
||||||
|
ctx.accounts.data.data = data;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn test_no_rent_exempt(ctx: Context<NoRentExempt>) -> Result<()> {
|
pub fn test_no_rent_exempt(ctx: Context<NoRentExempt>) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,6 +164,16 @@ describe("misc", () => {
|
||||||
assert.ok(resp.events[1].data.data === 1234);
|
assert.ok(resp.events[1].data.data === 1234);
|
||||||
assert.ok(resp.events[2].name === "E3");
|
assert.ok(resp.events[2].name === "E3");
|
||||||
assert.ok(resp.events[2].data.data === 9);
|
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;
|
let dataI8;
|
||||||
|
@ -1432,7 +1442,7 @@ describe("misc", () => {
|
||||||
it("Can use multidimensional array", async () => {
|
it("Can use multidimensional array", async () => {
|
||||||
const array2d = new Array(10).fill(new Array(10).fill(99));
|
const array2d = new Array(10).fill(new Array(10).fill(99));
|
||||||
const data = anchor.web3.Keypair.generate();
|
const data = anchor.web3.Keypair.generate();
|
||||||
const tx = await program.rpc.testMultidimensionalArray(array2d, {
|
await program.rpc.testMultidimensionalArray(array2d, {
|
||||||
accounts: {
|
accounts: {
|
||||||
data: data.publicKey,
|
data: data.publicKey,
|
||||||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||||
|
@ -1448,6 +1458,28 @@ describe("misc", () => {
|
||||||
assert.deepStrictEqual(dataAccount.data, array2d);
|
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 () => {
|
it("allows non-rent exempt accounts", async () => {
|
||||||
const data = anchor.web3.Keypair.generate();
|
const data = anchor.web3.Keypair.generate();
|
||||||
await program.rpc.initializeNoRentExempt({
|
await program.rpc.initializeNoRentExempt({
|
||||||
|
|
Loading…
Reference in New Issue