Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
9 changes: 8 additions & 1 deletion crates/cargo-pvm-contract-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,17 @@ fn process_elf_binaries(
}

let output_path = profile_dir.join(format!("{bin}.polkavm"));
let abi_path = profile_dir.join(format!("{bin}.abi.json"));

link_to_polkavm(&elf_path, &output_path)?;

// Clear any previous `.abi.json`. The re-emit below skips writing
// when `generate_abi == false` (`PvmBuilder::skip_abi(true)`) or
// the source has no `#[contract]` macro, so without this cleanup
// a stale ABI would survive a `.polkavm` overwrite.
let _ = fs::remove_file(&abi_path);
Comment thread
re-gius marked this conversation as resolved.
Outdated

if generate_abi {
let abi_path = profile_dir.join(format!("{bin}.abi.json"));
generate_abi_file(manifest_dir, bin, &abi_path, abi_target_root, features)?;
}
}
Expand Down
32 changes: 32 additions & 0 deletions crates/cargo-pvm-contract/tests/fixtures/no_contract_macro.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Test fixture for `rebuild_without_contract_macro_clears_stale_abi_json`.
//
// A minimal polkavm-only contract source that compiles against the standard
// macro-scaffolded Cargo.toml (`pvm-contract-sdk` + `polkavm-derive`) but
// does not invoke the contract attribute macro, so the builder's
// `has_contract_macro` check returns false and `generate_abi_file` hits
// the `Ok(None)` arm without writing.

#![cfg_attr(not(test), no_main, no_std)]

use pvm_contract_sdk::U256;

#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
unsafe {
core::arch::asm!("unimp");
core::hint::unreachable_unchecked()
}
}

#[cfg(not(test))]
#[unsafe(no_mangle)]
#[polkavm_derive::polkavm_export]
pub extern "C" fn deploy() {}

#[cfg(not(test))]
#[unsafe(no_mangle)]
#[polkavm_derive::polkavm_export]
pub extern "C" fn call() {
let _u = U256::ZERO;
}
61 changes: 61 additions & 0 deletions crates/cargo-pvm-contract/tests/test_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -652,3 +652,64 @@ fn build_forwards_features_with_package_flag() {
"ws-feat-pkg.abi.json should exist — proves --features reached the abi-gen invocation too"
);
}

/// Regression test for paritytech/bugbounty_reports#78: stripping the
/// `#[contract]` macro from a previously-built project must not leave the
/// old `.abi.json` next to the freshly-linked `.polkavm`.
#[test]
fn rebuild_without_contract_macro_clears_stale_abi_json() {
let temp_dir = TempDir::new().expect("temp dir");
let name = "stale-abi-test";

let project_dir = scaffold_new_contract(&temp_dir, name, "macro", None);
build_project(&project_dir, "release");
verify_abi_json(&project_dir, name, "release");

let src_path = project_dir.join("src").join(format!("{name}.rs"));
let stripped = include_str!("fixtures/no_contract_macro.rs");
std::fs::write(&src_path, stripped).expect("rewrite source");

build_project(&project_dir, "release");

let abi_path = project_dir
.join("target")
.join("release")
.join(format!("{name}.abi.json"));
assert!(
!abi_path.exists(),
"stale .abi.json at {}",
abi_path.display()
);
verify_polkavm_binary(&project_dir, name, "release");
}

/// A no-op source edit must produce a byte-identical `.abi.json`.
/// Guards against a regression where cleanup runs but re-emission doesn't.
#[test]
fn rebuild_with_macro_keeps_abi_byte_stable() {
let temp_dir = TempDir::new().expect("temp dir");
let name = "fresh-abi-test";

let project_dir = scaffold_new_contract(&temp_dir, name, "macro", None);
build_project(&project_dir, "release");
verify_abi_json(&project_dir, name, "release");

let abi_path = project_dir
.join("target")
.join("release")
.join(format!("{name}.abi.json"));
let abi_v1 = std::fs::read(&abi_path).expect("read v1 abi");

let src_path = project_dir.join("src").join(format!("{name}.rs"));
let original = std::fs::read_to_string(&src_path).expect("read source");
std::fs::write(&src_path, format!("{original}\n// rebuild trigger\n")).expect("write source");

build_project(&project_dir, "release");
verify_abi_json(&project_dir, name, "release");

let abi_v2 = std::fs::read(&abi_path).expect("read v2 abi");
assert_eq!(
abi_v1, abi_v2,
"ABI bytes should be stable across a no-op rebuild"
);
}
Loading