Skip to content

Commit

Permalink
lang, ts: automatic client side pda derivation (#1331)
Browse files Browse the repository at this point in the history
  • Loading branch information
armaniferrante authored Jan 24, 2022
1 parent 1a2fd38 commit d8d7200
Show file tree
Hide file tree
Showing 31 changed files with 1,027 additions and 78 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ jobs:
path: tests/ido-pool
- cmd: cd tests/cfo && anchor run test-with-build
path: tests/cfo
- cmd: cd tests/auction-house && yarn && anchor test
path: tests/auction-house
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/setup/
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
[submodule "examples/permissioned-markets/deps/serum-dex"]
path = tests/permissioned-markets/deps/serum-dex
url = https://github.com/project-serum/serum-dex
[submodule "tests/auction-house"]
path = tests/auction-house
url = https://github.com/armaniferrante/auction-house
15 changes: 14 additions & 1 deletion cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ impl WithPath<Config> {
let cargo = Manifest::from_path(&path.join("Cargo.toml"))?;
let lib_name = cargo.lib_name()?;
let version = cargo.version();
let idl = anchor_syn::idl::file::parse(path.join("src/lib.rs"), version)?;
let idl = anchor_syn::idl::file::parse(
path.join("src/lib.rs"),
version,
self.features.seeds,
)?;
r.push(Program {
lib_name,
path,
Expand Down Expand Up @@ -243,6 +247,7 @@ impl<T> std::ops::DerefMut for WithPath<T> {
pub struct Config {
pub anchor_version: Option<String>,
pub solana_version: Option<String>,
pub features: FeaturesConfig,
pub registry: RegistryConfig,
pub provider: ProviderConfig,
pub programs: ProgramsConfig,
Expand All @@ -251,6 +256,11 @@ pub struct Config {
pub test: Option<Test>,
}

#[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct FeaturesConfig {
pub seeds: bool,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RegistryConfig {
pub url: String,
Expand Down Expand Up @@ -362,6 +372,7 @@ impl Config {
struct _Config {
anchor_version: Option<String>,
solana_version: Option<String>,
features: Option<FeaturesConfig>,
programs: Option<BTreeMap<String, BTreeMap<String, serde_json::Value>>>,
registry: Option<RegistryConfig>,
provider: Provider,
Expand Down Expand Up @@ -389,6 +400,7 @@ impl ToString for Config {
let cfg = _Config {
anchor_version: self.anchor_version.clone(),
solana_version: self.solana_version.clone(),
features: Some(self.features.clone()),
registry: Some(self.registry.clone()),
provider: Provider {
cluster: format!("{}", self.provider.cluster),
Expand Down Expand Up @@ -417,6 +429,7 @@ impl FromStr for Config {
Ok(Config {
anchor_version: cfg.anchor_version,
solana_version: cfg.solana_version,
features: cfg.features.unwrap_or_default(),
registry: cfg.registry.unwrap_or_default(),
provider: ProviderConfig {
cluster: cfg.provider.cluster.parse()?,
Expand Down
22 changes: 14 additions & 8 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ fn build_cwd_verifiable(
Ok(_) => {
// Build the idl.
println!("Extracting the IDL");
if let Ok(Some(idl)) = extract_idl("src/lib.rs") {
if let Ok(Some(idl)) = extract_idl(cfg, "src/lib.rs") {
// Write out the JSON file.
println!("Writing the IDL file");
let out_file = workspace_dir.join(format!("target/idl/{}.json", idl.name));
Expand Down Expand Up @@ -1135,7 +1135,7 @@ fn _build_cwd(
}

// Always assume idl is located at src/lib.rs.
if let Some(idl) = extract_idl("src/lib.rs")? {
if let Some(idl) = extract_idl(cfg, "src/lib.rs")? {
// JSON out path.
let out = match idl_out {
None => PathBuf::from(".").join(&idl.name).with_extension("json"),
Expand Down Expand Up @@ -1219,7 +1219,7 @@ fn verify(
}

// Verify IDL (only if it's not a buffer account).
if let Some(local_idl) = extract_idl("src/lib.rs")? {
if let Some(local_idl) = extract_idl(&cfg, "src/lib.rs")? {
if bin_ver.state != BinVerificationState::Buffer {
let deployed_idl = fetch_idl(cfg_override, program_id)?;
if local_idl != deployed_idl {
Expand Down Expand Up @@ -1383,12 +1383,12 @@ fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result<Idl> {
serde_json::from_slice(&s[..]).map_err(Into::into)
}

fn extract_idl(file: &str) -> Result<Option<Idl>> {
fn extract_idl(cfg: &WithPath<Config>, file: &str) -> Result<Option<Idl>> {
let file = shellexpand::tilde(file);
let manifest_from_path = std::env::current_dir()?.join(PathBuf::from(&*file).parent().unwrap());
let cargo = Manifest::discover_from_path(manifest_from_path)?
.ok_or_else(|| anyhow!("Cargo.toml not found"))?;
anchor_syn::idl::file::parse(&*file, cargo.version())
anchor_syn::idl::file::parse(&*file, cargo.version(), cfg.features.seeds)
}

fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
Expand All @@ -1415,7 +1415,7 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
} => idl_set_authority(cfg_override, program_id, address, new_authority),
IdlCommand::EraseAuthority { program_id } => idl_erase_authority(cfg_override, program_id),
IdlCommand::Authority { program_id } => idl_authority(cfg_override, program_id),
IdlCommand::Parse { file, out, out_ts } => idl_parse(file, out, out_ts),
IdlCommand::Parse { file, out, out_ts } => idl_parse(cfg_override, file, out, out_ts),
IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out),
}
}
Expand Down Expand Up @@ -1674,8 +1674,14 @@ fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl, idl_address: Pubkey)
Ok(())
}

fn idl_parse(file: String, out: Option<String>, out_ts: Option<String>) -> Result<()> {
let idl = extract_idl(&file)?.ok_or_else(|| anyhow!("IDL not parsed"))?;
fn idl_parse(
cfg_override: &ConfigOverride,
file: String,
out: Option<String>,
out_ts: Option<String>,
) -> Result<()> {
let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
let idl = extract_idl(&cfg, &file)?.ok_or_else(|| anyhow!("IDL not parsed"))?;
let out = match out {
None => OutFile::Stdout,
Some(out) => OutFile::File(PathBuf::from(out)),
Expand Down
1 change: 1 addition & 0 deletions lang/syn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ idl = []
hash = []
default = []
anchor-debug = []
seeds = []

[dependencies]
proc-macro2 = "1.0"
Expand Down
18 changes: 13 additions & 5 deletions lang/syn/src/idl/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ const DERIVE_NAME: &str = "Accounts";
const ERROR_CODE_OFFSET: u32 = 6000;

// Parse an entire interface file.
pub fn parse(filename: impl AsRef<Path>, version: String) -> Result<Option<Idl>> {
pub fn parse(
filename: impl AsRef<Path>,
version: String,
seeds_feature: bool,
) -> Result<Option<Idl>> {
let ctx = CrateContext::parse(filename)?;

let program_mod = match parse_program_mod(&ctx) {
Expand Down Expand Up @@ -52,7 +56,8 @@ pub fn parse(filename: impl AsRef<Path>, version: String) -> Result<Option<Idl>>
.collect::<Vec<_>>();
let accounts_strct =
accs.get(&method.anchor_ident.to_string()).unwrap();
let accounts = idl_accounts(accounts_strct, &accs);
let accounts =
idl_accounts(&ctx, accounts_strct, &accs, seeds_feature);
IdlInstruction {
name,
accounts,
Expand Down Expand Up @@ -91,7 +96,7 @@ pub fn parse(filename: impl AsRef<Path>, version: String) -> Result<Option<Idl>>
})
.collect();
let accounts_strct = accs.get(&anchor_ident.to_string()).unwrap();
let accounts = idl_accounts(accounts_strct, &accs);
let accounts = idl_accounts(&ctx, accounts_strct, &accs, seeds_feature);
IdlInstruction {
name,
accounts,
Expand Down Expand Up @@ -159,7 +164,7 @@ pub fn parse(filename: impl AsRef<Path>, version: String) -> Result<Option<Idl>>
.collect::<Vec<_>>();
// todo: don't unwrap
let accounts_strct = accs.get(&ix.anchor_ident.to_string()).unwrap();
let accounts = idl_accounts(accounts_strct, &accs);
let accounts = idl_accounts(&ctx, accounts_strct, &accs, seeds_feature);
IdlInstruction {
name: ix.ident.to_string().to_mixed_case(),
accounts,
Expand Down Expand Up @@ -494,8 +499,10 @@ fn to_idl_type(f: &syn::Field) -> IdlType {
}

fn idl_accounts(
ctx: &CrateContext,
accounts: &AccountsStruct,
global_accs: &HashMap<String, AccountsStruct>,
seeds_feature: bool,
) -> Vec<IdlAccountItem> {
accounts
.fields
Expand All @@ -505,7 +512,7 @@ fn idl_accounts(
let accs_strct = global_accs
.get(&comp_f.symbol)
.expect("Could not resolve Accounts symbol");
let accounts = idl_accounts(accs_strct, global_accs);
let accounts = idl_accounts(ctx, accs_strct, global_accs, seeds_feature);
IdlAccountItem::IdlAccounts(IdlAccounts {
name: comp_f.ident.to_string().to_mixed_case(),
accounts,
Expand All @@ -518,6 +525,7 @@ fn idl_accounts(
Ty::Signer => true,
_ => acc.constraints.is_signer(),
},
pda: pda::parse(ctx, accounts, acc, seeds_feature),
}),
})
.collect::<Vec<_>>()
Expand Down
50 changes: 49 additions & 1 deletion lang/syn/src/idl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;

pub mod file;
pub mod pda;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Idl {
Expand Down Expand Up @@ -66,6 +67,52 @@ pub struct IdlAccount {
pub name: String,
pub is_mut: bool,
pub is_signer: bool,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub pda: Option<IdlPda>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlPda {
pub seeds: Vec<IdlSeed>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub program_id: Option<IdlSeed>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase", tag = "kind")]
pub enum IdlSeed {
Const(IdlSeedConst),
Arg(IdlSeedArg),
Account(IdlSeedAccount),
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlSeedAccount {
#[serde(rename = "type")]
pub ty: IdlType,
// account_ty points to the entry in the "accounts" section.
// Some only if the `Account<T>` type is used.
#[serde(skip_serializing_if = "Option::is_none")]
pub account: Option<String>,
pub path: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlSeedArg {
#[serde(rename = "type")]
pub ty: IdlType,
pub path: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlSeedConst {
#[serde(rename = "type")]
pub ty: IdlType,
pub value: serde_json::Value,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
Expand Down Expand Up @@ -157,6 +204,7 @@ impl std::str::FromStr for IdlType {
}
}
s.retain(|c| !c.is_whitespace());

let r = match s.as_str() {
"bool" => IdlType::Bool,
"u8" => IdlType::U8,
Expand All @@ -170,7 +218,7 @@ impl std::str::FromStr for IdlType {
"u128" => IdlType::U128,
"i128" => IdlType::I128,
"Vec<u8>" => IdlType::Bytes,
"String" => IdlType::String,
"String" | "&str" => IdlType::String,
"Pubkey" => IdlType::PublicKey,
_ => match s.to_string().strip_prefix("Option<") {
None => match s.to_string().strip_prefix("Vec<") {
Expand Down
Loading

0 comments on commit d8d7200

Please sign in to comment.