#![expect(clippy::expect_used, clippy::panic)]
use std::path::{Path, PathBuf};
use serde_json::Value;
use sha2::{Digest, Sha256};
fn repo_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../../..")
.canonicalize()
.expect("canonicalize repo root")
}
fn oracle_root() -> PathBuf {
repo_root().join("tier3-oracle")
}
fn sha256_hex(path: &Path) -> String {
let bytes =
std::fs::read(path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
let digest = Sha256::digest(bytes);
let mut out = String::with_capacity(64);
for b in digest {
use std::fmt::Write as _;
let _ = write!(out, "{b:02x}");
}
out
}
#[test]
fn committed_executor_artifacts_match_tracked_source() {
let root = oracle_root();
let manifest_path = root.join("artifacts/executor/manifest.json");
let manifest_raw = std::fs::read_to_string(&manifest_path)
.unwrap_or_else(|e| panic!("missing {}: {e}", manifest_path.display()));
let manifest: Value = serde_json::from_str(&manifest_raw)
.unwrap_or_else(|e| panic!("invalid manifest {}: {e}", manifest_path.display()));
let map = manifest["artifacts"]
.as_object()
.unwrap_or_else(|| panic!("manifest missing `artifacts` object"));
assert!(
!map.is_empty(),
"executor manifest {} lists no artifacts — run tier3-oracle/build-tier3-executor-harness.sh",
manifest_path.display()
);
for (artifact, meta) in map {
let expected_src = meta
.get("source")
.and_then(Value::as_str)
.unwrap_or_else(|| panic!("manifest entry {artifact} missing `source`"));
let expected_hash = meta
.get("sha256")
.and_then(Value::as_str)
.unwrap_or_else(|| panic!("manifest entry {artifact} missing `sha256`"));
let source_path = repo_root().join(expected_src);
let actual_hash = sha256_hex(&source_path);
assert_eq!(
actual_hash,
expected_hash,
"executor source {} changed but artifacts/executor/manifest.json was not refreshed. \
Re-run tier3-oracle/build-tier3-executor-harness.sh (PUBLISH=1) and commit the \
updated artifact + manifest.",
source_path.display()
);
let artifact_path = root.join("artifacts").join(artifact);
let raw = std::fs::read_to_string(&artifact_path).unwrap_or_else(|e| {
panic!(
"missing committed artifact {}: {e}",
artifact_path.display()
)
});
let is_hex = Path::new(artifact)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("hex"));
if is_hex {
let hex = raw.trim();
assert!(
!hex.is_empty() && hex.len().is_multiple_of(2),
"{} has empty/non-hex creation bytecode",
artifact_path.display()
);
assert!(
hex.chars().all(|c| c.is_ascii_hexdigit()),
"{} contains non-hex chars",
artifact_path.display()
);
} else if artifact.contains(".sol/") {
let v: Value = serde_json::from_str(&raw)
.unwrap_or_else(|e| panic!("{} is invalid json: {e}", artifact_path.display()));
let obj = v["bytecode"]["object"].as_str();
assert!(
obj.is_some(),
"{} has no bytecode.object (foundry shape)",
artifact_path.display()
);
}
}
}