Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions lang-v2/derive/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,16 +793,6 @@ fn rewrite_seed_value_expr(expr: &Expr, field_names: &[String]) -> proc_macro2::
}
}
}
if let Expr::MethodCall(method_call) = expr {
if method_call.method == "as_ref"
&& method_call.args.is_empty()
&& method_call.turbofish.is_none()
&& !matches!(method_call.receiver.as_ref(), Expr::Path(_))
{
let receiver = &method_call.receiver;
return quote! { #receiver };
}
}
quote! { #expr }
}

Expand Down Expand Up @@ -1178,10 +1168,11 @@ fn emit_init_body(
__seed_ref, #pda_program, &__target.address(),
).map_err(|_| anchor_lang_v2::ErrorCode::ConstraintSeeds)?;
__bumps.#field_name = #bump_assign;
let __bump_bytes = [__bump];
let mut __seed_buf: [&[u8]; 17] = [&[]; 17];
let __n = __seed_ref.len();
__seed_buf[..__n].copy_from_slice(__seed_ref);
__seed_buf[__n] = &[__bump];
__seed_buf[__n] = &__bump_bytes;
let __seeds: Option<&[&[u8]]> = Some(&__seed_buf[..__n + 1]);
}
}
Expand Down Expand Up @@ -2394,6 +2385,16 @@ mod tests {
);
}

#[test]
fn rewrite_seed_value_expr_preserves_nontrivial_as_ref_receivers() {
let expr: Expr = syn::parse_quote!(config.seed.as_ref());
let fields = vec!["config".to_string()];

let rewritten = rewrite_seed_value_expr(&expr, &fields);

assert_eq!(rewritten.to_string(), "config . seed . as_ref ()");
}

#[test]
fn init_with_explicit_bump_is_rejected() {
// Mirrors Anchor v1: `init` requires the canonical bump (off-curve
Expand Down
124 changes: 121 additions & 3 deletions lang-v2/tests/macro_diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{fs, path::PathBuf, process::Command};

fn compile_fail_case(name: &str, source: &str, snippets: &[&str]) {
fn cargo_check_case(name: &str, source: &str) -> std::process::Output {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let crate_dir = manifest_dir.join("target/macro-diagnostics").join(name);
let src_dir = crate_dir.join("src");
Expand All @@ -26,11 +26,15 @@ wincode = {{ version = "0.5", features = ["derive"] }}
.unwrap();
fs::write(src_dir.join("lib.rs"), source).unwrap();

let output = Command::new("cargo")
Command::new("cargo")
.args(["check", "--offline", "--manifest-path"])
.arg(crate_dir.join("Cargo.toml"))
.output()
.unwrap_or_else(|err| panic!("failed to run cargo check for {name}: {err}"));
.unwrap_or_else(|err| panic!("failed to run cargo check for {name}: {err}"))
}

fn compile_fail_case(name: &str, source: &str, snippets: &[&str]) {
let output = cargo_check_case(name, source);

assert!(
!output.status.success(),
Expand All @@ -45,6 +49,17 @@ wincode = {{ version = "0.5", features = ["derive"] }}
}
}

fn compile_pass_case(name: &str, source: &str) {
let output = cargo_check_case(name, source);

assert!(
output.status.success(),
"{name} failed to compile\n\nstdout:\n{}\n\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}

#[test]
#[cfg_attr(
miri,
Expand Down Expand Up @@ -237,6 +252,109 @@ fn check() {
);
}

#[test]
#[cfg_attr(
miri,
ignore = "spawns cargo and writes temporary workspaces; covered by normal cargo test"
)]
fn seeds_preserve_nontrivial_as_ref_receivers() {
compile_pass_case(
"seed_nontrivial_as_ref",
r#"
use anchor_lang_v2::prelude::*;

declare_id!("11111111111111111111111111111111");

#[derive(anchor_lang_v2::wincode::SchemaRead, anchor_lang_v2::wincode::SchemaWrite)]
pub struct SeedBuf(Vec<u8>);

impl SeedBuf {
pub fn as_ref(&self) -> &[u8] {
self.0.as_slice()
}
}

#[derive(anchor_lang_v2::wincode::SchemaRead, anchor_lang_v2::wincode::SchemaWrite)]
pub struct SeedConfig {
pub seed: SeedBuf,
}

impl Owner for SeedConfig {
const OWNER: Address = crate::ID;
}

impl Discriminator for SeedConfig {
const DISCRIMINATOR: &'static [u8] = &[0x63, 0x66, 0x67, 0x2d, 0x73, 0x65, 0x65, 0x64];
}

#[derive(Accounts)]
pub struct Good {
pub config: BorshAccount<SeedConfig>,
#[account(seeds = [config.seed.as_ref()], bump)]
pub target: UncheckedAccount,
}
"#,
);
}

#[test]
#[cfg_attr(
miri,
ignore = "spawns cargo and writes temporary workspaces; covered by normal cargo test"
)]
fn init_opaque_seed_expressions_keep_bump_bytes_alive() {
compile_pass_case(
"init_opaque_seed_expr",
r#"
use anchor_lang_v2::prelude::*;

declare_id!("11111111111111111111111111111111");

#[derive(anchor_lang_v2::wincode::SchemaRead, anchor_lang_v2::wincode::SchemaWrite)]
pub struct Data {
pub value: u64,
}

impl Owner for Data {
const OWNER: Address = crate::ID;
}

impl Discriminator for Data {
const DISCRIMINATOR: &'static [u8] = &[0x64, 0x61, 0x74, 0x61, 0x2d, 0x62, 0x6f, 0x72];
}

pub struct SeedBundle<'a>([&'a [u8]; 1]);

impl<'a> SeedBundle<'a> {
pub fn for_payer(payer: &'a [u8]) -> Self {
Self([payer])
}
}

impl<'a> AsRef<[&'a [u8]]> for SeedBundle<'a> {
fn as_ref(&self) -> &[&'a [u8]] {
&self.0
}
}

#[derive(Accounts)]
pub struct Good {
#[account(mut)]
pub payer: Signer,
#[account(
init,
payer = payer,
space = 8 + core::mem::size_of::<Data>(),
seeds = SeedBundle::for_payer(payer.address().as_ref()),
bump
)]
pub data: BorshAccount<Data>,
pub system_program: Program<System>,
}
"#,
);
}

#[test]
#[cfg_attr(
miri,
Expand Down
153 changes: 153 additions & 0 deletions tests-v2/tests/pda_seed_expr.rs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests were redundant please remove one set of tests

Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
use std::{fs, path::PathBuf, process::Command};

fn compile_pass_case(name: &str, source: &str) {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let crate_dir = manifest_dir.join("target/compile-cases").join(name);
let src_dir = crate_dir.join("src");
let anchor_lang_v2 = manifest_dir
.parent()
.expect("tests-v2 should live under the workspace root")
.join("lang-v2");

if crate_dir.exists() {
fs::remove_dir_all(&crate_dir).unwrap();
}
fs::create_dir_all(&src_dir).unwrap();

fs::write(
crate_dir.join("Cargo.toml"),
format!(
r#"[package]
name = "{name}"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
anchor-lang-v2 = {{ path = "{}" }}
wincode = {{ version = "0.5", features = ["derive"] }}

[workspace]
"#,
anchor_lang_v2.display()
),
)
.unwrap();
fs::write(src_dir.join("lib.rs"), source).unwrap();

let output = Command::new("cargo")
.args(["check", "--offline", "--manifest-path"])
.arg(crate_dir.join("Cargo.toml"))
.output()
.unwrap_or_else(|err| panic!("failed to run cargo check for {name}: {err}"));

assert!(
output.status.success(),
"{name} failed to compile\n\nstdout:\n{}\n\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}

#[test]
#[cfg_attr(
miri,
ignore = "spawns cargo and writes temporary workspaces; covered by normal cargo test"
)]
fn nontrivial_as_ref_seed_receivers_compile() {
compile_pass_case(
"tests_v2_seed_nontrivial_as_ref",
r#"
use anchor_lang_v2::prelude::*;

declare_id!("11111111111111111111111111111111");

#[derive(anchor_lang_v2::wincode::SchemaRead, anchor_lang_v2::wincode::SchemaWrite)]
pub struct SeedBuf(Vec<u8>);

impl SeedBuf {
pub fn as_ref(&self) -> &[u8] {
self.0.as_slice()
}
}

#[derive(anchor_lang_v2::wincode::SchemaRead, anchor_lang_v2::wincode::SchemaWrite)]
pub struct SeedConfig {
pub seed: SeedBuf,
}

impl Owner for SeedConfig {
const OWNER: Address = crate::ID;
}

impl Discriminator for SeedConfig {
const DISCRIMINATOR: &'static [u8] = &[0x63, 0x66, 0x67, 0x2d, 0x73, 0x65, 0x65, 0x64];
}

#[derive(Accounts)]
pub struct Good {
pub config: BorshAccount<SeedConfig>,
#[account(seeds = [config.seed.as_ref()], bump)]
pub target: UncheckedAccount,
}
"#,
);
}

#[test]
#[cfg_attr(
miri,
ignore = "spawns cargo and writes temporary workspaces; covered by normal cargo test"
)]
fn opaque_init_seed_expressions_keep_bump_bytes_alive() {
compile_pass_case(
"tests_v2_init_opaque_seed_expr",
r#"
use anchor_lang_v2::prelude::*;

declare_id!("11111111111111111111111111111111");

#[derive(anchor_lang_v2::wincode::SchemaRead, anchor_lang_v2::wincode::SchemaWrite)]
pub struct Data {
pub value: u64,
}

impl Owner for Data {
const OWNER: Address = crate::ID;
}

impl Discriminator for Data {
const DISCRIMINATOR: &'static [u8] = &[0x64, 0x61, 0x74, 0x61, 0x2d, 0x62, 0x6f, 0x72];
}

pub struct SeedBundle<'a>([&'a [u8]; 1]);

impl<'a> SeedBundle<'a> {
pub fn for_payer(payer: &'a [u8]) -> Self {
Self([payer])
}
}

impl<'a> AsRef<[&'a [u8]]> for SeedBundle<'a> {
fn as_ref(&self) -> &[&'a [u8]] {
&self.0
}
}

#[derive(Accounts)]
pub struct Good {
#[account(mut)]
pub payer: Signer,
#[account(
init,
payer = payer,
space = 8 + core::mem::size_of::<Data>(),
seeds = SeedBundle::for_payer(payer.address().as_ref()),
bump
)]
pub data: BorshAccount<Data>,
pub system_program: Program<System>,
}
"#,
);
}
Loading