// halo2-cubic-verifier.test.js
//
// Real Halo2 (bn254/KZG, Bdfg21/SHPLONK) verifier generated by
// privacy-scaling-explorations/halo2-solidity-verifier on the AERE build
// server, for a genuine 4-row standard-PLONK circuit proving knowledge of a
// private x with x^3 + x + 5 == out (witness x = 3, public instance out = 35).
//
// The proof in the fixture is a REAL halo2 proof: natively verified by
// halo2_proofs (VerifierSHPLONK) and re-verified in revm before it was
// exported. These tests re-verify it a third time inside the hardhat EVM.

const { expect } = require("chai");
const { ethers } = require("hardhat");
const fixture = require("./fixtures/aere-halo2-cubic-proof.json");

const EIP7825_TX_GAS_CAP = 16777216;

describe("AereHalo2CubicVerifier + AereHalo2ProofAnchor", function () {
  let verifier, anchor, user;
  const instances = fixture.instances.map(BigInt); // [35n]

  before(async function () {
    [user] = await ethers.getSigners();

    const V = await ethers.getContractFactory("AereHalo2CubicVerifier");
    verifier = await V.deploy();
    await verifier.waitForDeployment();

    const A = await ethers.getContractFactory("AereHalo2ProofAnchor");
    anchor = await A.deploy(await verifier.getAddress());
    await anchor.waitForDeployment();
  });

  it("verifies the real proof via the exact exported calldata", async function () {
    const raw = await ethers.provider.call({
      to: await verifier.getAddress(),
      data: fixture.calldata,
    });
    expect(raw).to.equal(
      "0x0000000000000000000000000000000000000000000000000000000000000001"
    );
  });

  it("verifies the real proof via the typed ABI", async function () {
    expect(
      await verifier.verifyProof.staticCall(fixture.proof, instances)
    ).to.equal(true);
  });

  it("verification gas fits comfortably under the EIP-7825 per-tx cap", async function () {
    const gas = await verifier.verifyProof.estimateGas(fixture.proof, instances);
    expect(gas).to.be.lt(BigInt(EIP7825_TX_GAS_CAP));
  });

  it("rejects a corrupted proof (byte flipped)", async function () {
    const bad = ethers.getBytes(fixture.proof);
    bad[100] ^= 0x01;
    await expect(
      verifier.verifyProof.staticCall(ethers.hexlify(bad), instances)
    ).to.be.reverted;
  });

  it("rejects a truncated proof", async function () {
    const bad = ethers.getBytes(fixture.proof).slice(0, 64);
    await expect(
      verifier.verifyProof.staticCall(ethers.hexlify(bad), instances)
    ).to.be.reverted;
  });

  it("rejects the right proof against the wrong public instance", async function () {
    await expect(
      verifier.verifyProof.staticCall(fixture.proof, [34n])
    ).to.be.reverted;
    await expect(
      verifier.verifyProof.staticCall(fixture.proof, [36n])
    ).to.be.reverted;
  });

  it("anchor records a successful verification and emits the event", async function () {
    const proofHash = ethers.keccak256(fixture.proof);
    const instancesHash = ethers.solidityPackedKeccak256(
      ["uint256[]"],
      [instances]
    );

    await expect(anchor.verifyAndRecord(fixture.proof, instances))
      .to.emit(anchor, "Halo2ProofVerified")
      .withArgs(user.address, proofHash, instancesHash, 0n);

    expect(await anchor.recordCount()).to.equal(1n);
    const rec = await anchor.getRecord(0);
    expect(rec.sender).to.equal(user.address);
    expect(rec.proofHash).to.equal(proofHash);
    expect(rec.instancesHash).to.equal(instancesHash);
    expect(await anchor.verificationCountByProofHash(proofHash)).to.equal(1n);
  });

  it("anchor rejects an invalid proof (verifier revert bubbles up)", async function () {
    const bad = ethers.getBytes(fixture.proof);
    bad[200] ^= 0x01;
    await expect(anchor.verifyAndRecord(ethers.hexlify(bad), instances)).to.be
      .reverted;
    expect(await anchor.recordCount()).to.equal(1n);
  });

  it("anchor refuses a zero verifier address", async function () {
    const A = await ethers.getContractFactory("AereHalo2ProofAnchor");
    await expect(A.deploy(ethers.ZeroAddress)).to.be.revertedWithCustomError(
      A,
      "ZeroVerifier"
    );
  });
});
