feat: constants declared in impl blocks in seeds (#2128)

This commit is contained in:
Sammy Harris 2022-08-14 12:42:16 -04:00 committed by GitHub
parent fc9c680d6a
commit 290b2aa43e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 91 additions and 13 deletions

View File

@ -15,6 +15,7 @@ The minor version will be incremented upon a breaking change and the patch versi
* client: Add `transaction` functions to RequestBuilder ([#1958](https://github.com/coral-xyz/anchor/pull/1958)).
* spl: Add `create_metadata_accounts_v3` and `set_collection_size` wrappers ([#2119](https://github.com/coral-xyz/anchor/pull/2119))
* spl: Add `MetadataAccount` account deserialization. ([#2014](https://github.com/coral-xyz/anchor/pull/2014)).
* lang: Add parsing for consts from impl blocks for IDL PDA seeds generation ([#2128](https://github.com/coral-xyz/anchor/pull/2014))
### Fixes

View File

@ -234,7 +234,7 @@ impl std::str::FromStr for IdlType {
"u128" => IdlType::U128,
"i128" => IdlType::I128,
"Vec<u8>" => IdlType::Bytes,
"String" | "&str" => IdlType::String,
"String" | "&str" | "&'staticstr" => IdlType::String,
"Pubkey" => IdlType::PublicKey,
_ => match s.to_string().strip_prefix("Option<") {
None => match s.to_string().strip_prefix("Vec<") {

View File

@ -56,6 +56,8 @@ struct PdaParser<'a> {
ix_args: HashMap<String, String>,
// Constants available in the crate.
const_names: Vec<String>,
// Constants declared in impl blocks available in the crate
impl_const_names: Vec<String>,
// All field names of the accounts in the accounts context.
account_field_names: Vec<String>,
}
@ -65,6 +67,12 @@ impl<'a> PdaParser<'a> {
// All the available sources of seeds.
let ix_args = accounts.instruction_args().unwrap_or_default();
let const_names: Vec<String> = ctx.consts().map(|c| c.ident.to_string()).collect();
let impl_const_names: Vec<String> = ctx
.impl_consts()
.map(|(ident, item)| format!("{} :: {}", ident, item.ident))
.collect();
let account_field_names = accounts.field_names();
Self {
@ -72,6 +80,7 @@ impl<'a> PdaParser<'a> {
accounts,
ix_args,
const_names,
impl_const_names,
account_field_names,
}
}
@ -83,7 +92,6 @@ impl<'a> PdaParser<'a> {
.iter()
.map(|s| self.parse_seed(s))
.collect::<Option<Vec<_>>>()?;
// Parse the program id from the constraints.
let program_id = seeds_grp
.program_seed
@ -104,6 +112,8 @@ impl<'a> PdaParser<'a> {
self.parse_instruction(&seed_path)
} else if self.is_const(&seed_path) {
self.parse_const(&seed_path)
} else if self.is_impl_const(&seed_path) {
self.parse_impl_const(&seed_path)
} else if self.is_account(&seed_path) {
self.parse_account(&seed_path)
} else if self.is_str_literal(&seed_path) {
@ -150,18 +160,30 @@ impl<'a> PdaParser<'a> {
.find(|c| c.ident == seed_path.name())
.unwrap();
let idl_ty = IdlType::from_str(&parser::tts_to_string(&const_item.ty)).ok()?;
let mut idl_ty_value = parser::tts_to_string(&const_item.expr);
if let IdlType::Array(_ty, _size) = &idl_ty {
// Convert str literal to array.
if idl_ty_value.contains("b\"") {
let components: Vec<&str> = idl_ty_value.split('b').collect();
assert!(components.len() == 2);
let mut str_lit = components[1].to_string();
str_lit.retain(|c| c != '"');
idl_ty_value = format!("{:?}", str_lit.as_bytes());
}
}
let idl_ty_value = parser::tts_to_string(&const_item.expr);
let idl_ty_value = str_lit_to_array(&idl_ty, &idl_ty_value);
Some(IdlSeed::Const(IdlSeedConst {
ty: idl_ty,
value: serde_json::from_str(&idl_ty_value).unwrap(),
}))
}
fn parse_impl_const(&self, seed_path: &SeedPath) -> Option<IdlSeed> {
// Pull in the constant value directly into the IDL.
assert!(seed_path.components().is_empty());
let static_item = self
.ctx
.impl_consts()
.find(|(ident, item)| format!("{} :: {}", ident, item.ident) == seed_path.name())
.unwrap()
.1;
let idl_ty = IdlType::from_str(&parser::tts_to_string(&static_item.ty)).ok()?;
let idl_ty_value = parser::tts_to_string(&static_item.expr);
let idl_ty_value = str_lit_to_array(&idl_ty, &idl_ty_value);
Some(IdlSeed::Const(IdlSeedConst {
ty: idl_ty,
@ -236,6 +258,10 @@ impl<'a> PdaParser<'a> {
self.const_names.contains(&seed_path.name())
}
fn is_impl_const(&self, seed_path: &SeedPath) -> bool {
self.impl_const_names.contains(&seed_path.name())
}
fn is_account(&self, seed_path: &SeedPath) -> bool {
self.account_field_names.contains(&seed_path.name())
}
@ -327,3 +353,17 @@ fn parse_field_path(ctx: &CrateContext, strct: &syn::ItemStruct, path: &mut &[St
parse_field_path(ctx, strct, path)
}
fn str_lit_to_array(idl_ty: &IdlType, idl_ty_value: &String) -> String {
if let IdlType::Array(_ty, _size) = &idl_ty {
// Convert str literal to array.
if idl_ty_value.contains("b\"") {
let components: Vec<&str> = idl_ty_value.split('b').collect();
assert_eq!(components.len(), 2);
let mut str_lit = components[1].to_string();
str_lit.retain(|c| c != '"');
return format!("{:?}", str_lit.as_bytes());
}
}
idl_ty_value.to_string()
}

View File

@ -2,6 +2,7 @@ use anyhow::anyhow;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use syn::parse::{Error as ParseError, Result as ParseResult};
use syn::{Ident, ImplItem, ImplItemConst, Type, TypePath};
/// Crate parse context
///
@ -15,6 +16,10 @@ impl CrateContext {
self.modules.iter().flat_map(|(_, ctx)| ctx.consts())
}
pub fn impl_consts(&self) -> impl Iterator<Item = (&Ident, &syn::ImplItemConst)> {
self.modules.iter().flat_map(|(_, ctx)| ctx.impl_consts())
}
pub fn structs(&self) -> impl Iterator<Item = &syn::ItemStruct> {
self.modules.iter().flat_map(|(_, ctx)| ctx.structs())
}
@ -244,4 +249,36 @@ impl ParsedModule {
_ => None,
})
}
fn impl_consts(&self) -> impl Iterator<Item = (&Ident, &ImplItemConst)> {
self.items
.iter()
.filter_map(|i| match i {
syn::Item::Impl(syn::ItemImpl {
self_ty: ty, items, ..
}) => {
if let Type::Path(TypePath {
qself: None,
path: p,
}) = ty.as_ref()
{
if let Some(ident) = p.get_ident() {
let mut to_return = Vec::new();
items.iter().for_each(|item| {
if let ImplItem::Const(item) = item {
to_return.push((ident, item));
}
});
Some(to_return)
} else {
None
}
} else {
None
}
}
_ => None,
})
.flatten()
}
}