const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("AereAttestationGateway", () => { let foundation, bafin, finma, subject1, outsider; let gw; const SCHEMA_MICA = ethers.keccak256(ethers.toUtf8Bytes("mica-casp-v1")); const SCHEMA_AIACT = ethers.keccak256(ethers.toUtf8Bytes("ai-act-annex3-v1")); const SPEC_HASH_MICA = ethers.keccak256(ethers.toUtf8Bytes("mica-spec-bytes")); const SPEC_HASH_AI = ethers.keccak256(ethers.toUtf8Bytes("ai-act-spec-bytes")); const SUBJECT_A = ethers.keccak256(ethers.toUtf8Bytes("subject:exchange-A")); const PAYLOAD_HASH = ethers.keccak256(ethers.toUtf8Bytes("payload-bytes")); beforeEach(async () => { [foundation, bafin, finma, subject1, outsider] = await ethers.getSigners(); const F = await ethers.getContractFactory("AereAttestationGateway"); gw = await F.deploy(foundation.address); await gw.waitForDeployment(); }); async function nowTs() { return (await ethers.provider.getBlock("latest")).timestamp; } it("only Foundation publishes schemas; cannot republish", async () => { await expect( gw.connect(outsider).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "mica-casp-v1", "1.0.0", "ipfs://") ).to.be.revertedWithCustomError(gw, "NotFoundation"); await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "mica-casp-v1", "1.0.0", "ipfs://"); expect((await gw.schemas(SCHEMA_MICA)).exists).to.equal(true); await expect( gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "x", "y", "z") ).to.be.revertedWithCustomError(gw, "SchemaExists"); await expect( gw.connect(foundation).publishSchema(SCHEMA_AIACT, ethers.ZeroHash, "n", "v", "u") ).to.be.revertedWithCustomError(gw, "EmptySpec"); }); it("rejects attest from non-authorised attestor + unknown schema", async () => { await expect( gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, 0, 0, "") ).to.be.revertedWithCustomError(gw, "SchemaUnknown"); await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u"); await expect( gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, 0, 0, "") ).to.be.revertedWithCustomError(gw, "NotAuthorisedAttestor"); }); it("authorise + attest emits + records + latestFor + subjectCurrentlyAttested", async () => { await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u"); await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true); const t = await nowTs(); await expect( gw.connect(bafin).attest( SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t, t + 3600, "ipfs://x" ) ).to.emit(gw, "Attested"); const id = await gw.latestFor(SUBJECT_A, SCHEMA_MICA); const a = await gw.attestationOf(id); expect(a.attestor).to.equal(bafin.address); expect(a.payloadHash).to.equal(PAYLOAD_HASH); expect(await gw.isValidNow(id)).to.equal(true); expect(await gw.subjectCurrentlyAttested(SUBJECT_A, SCHEMA_MICA)).to.equal(true); }); it("validity window — past validUntil is invalid", async () => { await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u"); await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true); const t = await nowTs(); await gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t, t + 10, ""); await ethers.provider.send("evm_increaseTime", [20]); await ethers.provider.send("evm_mine", []); const id = await gw.latestFor(SUBJECT_A, SCHEMA_MICA); expect(await gw.isValidNow(id)).to.equal(false); }); it("bad validity window (validUntil < validFrom) rejected", async () => { await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u"); await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true); const t = await nowTs(); await expect( gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t + 100, t, "") ).to.be.revertedWithCustomError(gw, "BadValidityWindow"); }); it("revocation: only issuer; immediate invalidity; cannot revoke twice", async () => { await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u"); await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true); const t = await nowTs(); await gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t, 0, ""); const id = await gw.latestFor(SUBJECT_A, SCHEMA_MICA); await expect(gw.connect(outsider).revoke(id)).to.be.revertedWithCustomError(gw, "NotIssuer"); await gw.connect(bafin).revoke(id); expect(await gw.isValidNow(id)).to.equal(false); await expect(gw.connect(bafin).revoke(id)).to.be.revertedWithCustomError(gw, "AlreadyRevoked"); }); it("inheritance: child invalid if parent revoked OR expired", async () => { // KYB schema by Foundation; child = best-ex schema by BAFIN. const SCHEMA_KYB = ethers.keccak256(ethers.toUtf8Bytes("foundation-kyb-v1")); await gw.connect(foundation).publishSchema(SCHEMA_KYB, SPEC_HASH_MICA, "kyb", "1", "u"); await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u"); await gw.connect(foundation).setAttestor(SCHEMA_KYB, foundation.address, true); await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true); const t = await nowTs(); await gw.connect(foundation).attest(SCHEMA_KYB, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t, 0, ""); const parent = await gw.latestFor(SUBJECT_A, SCHEMA_KYB); await gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, parent, PAYLOAD_HASH, t, 0, ""); const child = await gw.latestFor(SUBJECT_A, SCHEMA_MICA); expect(await gw.isValidNow(child)).to.equal(true); // Revoke parent → child fails. await gw.connect(foundation).revoke(parent); expect(await gw.isValidNow(child)).to.equal(false); }); it("inheritance: bad parent rejected at attest time", async () => { await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u"); await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true); const fakeParent = ethers.keccak256(ethers.toUtf8Bytes("nonexistent")); await expect( gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, fakeParent, PAYLOAD_HASH, 0, 0, "") ).to.be.revertedWithCustomError(gw, "ParentInvalid"); }); it("inheritance: parent must match subject (audit fix HIGH #23)", async () => { const SCHEMA_KYB = ethers.keccak256(ethers.toUtf8Bytes("kyb")); await gw.connect(foundation).publishSchema(SCHEMA_KYB, SPEC_HASH_MICA, "kyb", "1", "u"); await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u"); await gw.connect(foundation).setAttestor(SCHEMA_KYB, foundation.address, true); await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true); const SUBJECT_B = ethers.keccak256(ethers.toUtf8Bytes("subject:exchange-B")); // KYB issued for SUBJECT_B (not SUBJECT_A). await gw.connect(foundation).attest(SCHEMA_KYB, SUBJECT_B, ethers.ZeroHash, PAYLOAD_HASH, 0, 0, ""); const parent = await gw.latestFor(SUBJECT_B, SCHEMA_KYB); // Trying to chain that parent for SUBJECT_A → rejected. await expect( gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, parent, PAYLOAD_HASH, 0, 0, "") ).to.be.revertedWithCustomError(gw, "ParentSubjectMismatch"); }); it("toggling attestor off does not retro-invalidate past attestations", async () => { await gw.connect(foundation).publishSchema(SCHEMA_MICA, SPEC_HASH_MICA, "n", "v", "u"); await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, true); const t = await nowTs(); await gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t, 0, ""); const id = await gw.latestFor(SUBJECT_A, SCHEMA_MICA); // De-authorise BAFIN going forward. await gw.connect(foundation).setAttestor(SCHEMA_MICA, bafin.address, false); // Past attestation is still valid (only revocation by issuer kills it). expect(await gw.isValidNow(id)).to.equal(true); // But BAFIN can no longer issue new attestations under this schema. await expect( gw.connect(bafin).attest(SCHEMA_MICA, SUBJECT_A, ethers.ZeroHash, PAYLOAD_HASH, t, 0, "") ).to.be.revertedWithCustomError(gw, "NotAuthorisedAttestor"); }); });