aere-contracts/test/ERC8004V2.test.js
Aere Network 1b50878368
Some checks failed
contracts-ci / Install (lockfile) → compile → full test suite (push) Failing after 16m0s
contracts-ci / Ethereum interop (EIP-2537 BLS, prague hardfork) (push) Failing after 27s
contracts-ci / PQC known-answer tests (NIST vectors) (push) Failing after 16m53s
contracts-ci / Coverage (scoped, with artifacts) (push) Has been cancelled
ERC-8004 V2 registries transcribed from the current draft (identity ERC-721+metadata+agentWallet, reputation, validation), 17 unit tests, local conformance deployment script; fixtures provenance; the MPC verifier moves to contracts/mpc (the publish gate refuses any 'confidential' path by structure)
2026-09-02 17:52:51 +03:00

213 lines
16 KiB
JavaScript

// ERC-8004 V2 registries (identity / reputation / validation), transcribed from the draft text.
// Every test names the draft sentence it enforces; negative cases are the draft's MUST NOTs.
const { expect } = require("chai");
const { ethers } = require("hardhat");
const IFACE = { erc165: "0x01ffc9a7", erc721: "0x80ac58cd", erc721Metadata: "0x5b5e139f" };
describe("ERC-8004 V2 registries (draft-conformant)", function () {
let idr, rep, val, owner, other, client, validator, wallet2;
beforeEach(async function () {
[owner, other, client, validator, wallet2] = await ethers.getSigners();
idr = await (await ethers.getContractFactory("AereIdentityRegistry8004V2")).deploy();
await idr.waitForDeployment();
rep = await (await ethers.getContractFactory("AereReputationRegistry8004V2")).deploy(await idr.getAddress());
val = await (await ethers.getContractFactory("AereValidationRegistry8004V2")).deploy(ethers.ZeroAddress);
await val.initialize(await idr.getAddress());
});
describe("Identity Registry", function () {
it("is an ERC-721 with URIStorage: ERC-165 ids for 165, 721 and 721Metadata", async function () {
for (const id of Object.values(IFACE)) expect(await idr.supportsInterface(id)).to.equal(true);
expect(await idr.supportsInterface("0xffffffff")).to.equal(false);
});
it("register(string): mints to the caller, sets tokenURI, initialises agentWallet to the owner, emits Registered + MetadataSet(agentWallet)", async function () {
const tx = await idr.connect(owner)["register(string)"]("ipfs://agent-1");
await expect(tx).to.emit(idr, "Registered").withArgs(1, "ipfs://agent-1", owner.address);
await expect(tx).to.emit(idr, "MetadataSet").withArgs(1, "agentWallet", "agentWallet", ethers.AbiCoder.defaultAbiCoder().encode(["address"], [owner.address]));
expect(await idr.ownerOf(1)).to.equal(owner.address);
expect(await idr.tokenURI(1)).to.equal("ipfs://agent-1");
expect(await idr.getAgentWallet(1)).to.equal(owner.address);
expect(await idr.agentCount()).to.equal(1);
});
it("register() and register(string,(string,bytes)[]) overloads; metadata entries emit MetadataSet; ids never repeat", async function () {
await idr.connect(owner)["register()"]();
const tx = await idr.connect(other)["register(string,(string,bytes)[])"]("ipfs://a2", [{ metadataKey: "model", metadataValue: ethers.toUtf8Bytes("claude") }]);
await expect(tx).to.emit(idr, "MetadataSet").withArgs(2, "model", "model", ethers.hexlify(ethers.toUtf8Bytes("claude")));
expect(await idr.getMetadata(2, "model")).to.equal(ethers.hexlify(ethers.toUtf8Bytes("claude")));
expect(await idr.ownerOf(2)).to.equal(other.address);
expect(await idr.tokenURI(1)).to.equal("");
});
it("the reserved key agentWallet cannot be set through setMetadata or the register metadata array", async function () {
await idr.connect(owner)["register()"]();
await expect(idr.connect(owner).setMetadata(1, "agentWallet", "0x01")).to.be.revertedWithCustomError(idr, "ReservedMetadataKey");
await expect(idr.connect(owner)["register(string,(string,bytes)[])"]("", [{ metadataKey: "agentWallet", metadataValue: "0x01" }])).to.be.revertedWithCustomError(idr, "ReservedMetadataKey");
});
it("setAgentURI and setMetadata are owner-or-operator only; URIUpdated carries updatedBy", async function () {
await idr.connect(owner)["register()"]();
await expect(idr.connect(other).setAgentURI(1, "x")).to.be.revertedWithCustomError(idr, "NotAgentOwnerOrOperator");
await expect(idr.connect(owner).setAgentURI(1, "ipfs://new")).to.emit(idr, "URIUpdated").withArgs(1, "ipfs://new", owner.address);
await idr.connect(owner).setApprovalForAll(other.address, true);
await expect(idr.connect(other).setAgentURI(1, "ipfs://op")).to.emit(idr, "URIUpdated").withArgs(1, "ipfs://op", other.address);
await expect(idr.connect(client).setMetadata(1, "k", "0x02")).to.be.revertedWithCustomError(idr, "NotAgentOwnerOrOperator");
});
async function walletSig(signer, agentId, newWallet, ownerAddr, deadline) {
const net = await ethers.provider.getNetwork();
const domain = { name: "ERC8004IdentityRegistry", version: "1", chainId: net.chainId, verifyingContract: await idr.getAddress() };
const types = { AgentWalletSet: [{ name: "agentId", type: "uint256" }, { name: "newWallet", type: "address" }, { name: "owner", type: "address" }, { name: "deadline", type: "uint256" }] };
return signer.signTypedData(domain, types, { agentId, newWallet, owner: ownerAddr, deadline });
}
it("setAgentWallet: the NEW wallet proves control with an EIP-712 signature (EOA)", async function () {
await idr.connect(owner)["register()"]();
const deadline = Math.floor(Date.now() / 1000) + 3600;
const sig = await walletSig(wallet2, 1, wallet2.address, owner.address, deadline);
await expect(idr.connect(owner).setAgentWallet(1, wallet2.address, deadline, sig)).to.emit(idr, "MetadataSet");
expect(await idr.getAgentWallet(1)).to.equal(wallet2.address);
expect(await idr.getMetadata(1, "agentWallet")).to.equal(ethers.AbiCoder.defaultAbiCoder().encode(["address"], [wallet2.address]));
});
it("setAgentWallet negatives: wrong signer, expired deadline, signature bound to another owner, non-owner caller", async function () {
await idr.connect(owner)["register()"]();
const deadline = Math.floor(Date.now() / 1000) + 3600;
const wrong = await walletSig(other, 1, wallet2.address, owner.address, deadline); // signed by someone else
await expect(idr.connect(owner).setAgentWallet(1, wallet2.address, deadline, wrong)).to.be.revertedWithCustomError(idr, "InvalidWalletSignature");
const good = await walletSig(wallet2, 1, wallet2.address, owner.address, 1); // expired
await expect(idr.connect(owner).setAgentWallet(1, wallet2.address, 1, good)).to.be.revertedWithCustomError(idr, "DeadlinePassed");
const forOther = await walletSig(wallet2, 1, wallet2.address, other.address, deadline); // bound to a different owner
await expect(idr.connect(owner).setAgentWallet(1, wallet2.address, deadline, forOther)).to.be.revertedWithCustomError(idr, "InvalidWalletSignature");
const ok = await walletSig(wallet2, 1, wallet2.address, owner.address, deadline);
await expect(idr.connect(other).setAgentWallet(1, wallet2.address, deadline, ok)).to.be.revertedWithCustomError(idr, "NotAgentOwnerOrOperator");
});
it("setAgentWallet accepts an ERC-1271 contract wallet", async function () {
await idr.connect(owner)["register()"]();
const cw = await (await ethers.getContractFactory("Mock1271Wallet")).deploy(wallet2.address);
const cwAddr = await cw.getAddress();
const deadline = Math.floor(Date.now() / 1000) + 3600;
const sig = await walletSig(wallet2, 1, cwAddr, owner.address, deadline); // the contract wallet's owner signs
await idr.connect(owner).setAgentWallet(1, cwAddr, deadline, sig);
expect(await idr.getAgentWallet(1)).to.equal(cwAddr);
const bad = await walletSig(other, 1, cwAddr, owner.address, deadline);
await expect(idr.connect(owner).setAgentWallet(1, cwAddr, deadline, bad)).to.be.revertedWithCustomError(idr, "InvalidWalletSignature");
});
it("transfer clears agentWallet (must be re-verified by the new owner); unsetAgentWallet clears it too", async function () {
await idr.connect(owner)["register()"]();
await expect(idr.connect(owner).transferFrom(owner.address, other.address, 1)).to.emit(idr, "MetadataSet").withArgs(1, "agentWallet", "agentWallet", ethers.AbiCoder.defaultAbiCoder().encode(["address"], [ethers.ZeroAddress]));
expect(await idr.getAgentWallet(1)).to.equal(ethers.ZeroAddress);
await idr.connect(other)["register()"]();
expect(await idr.getAgentWallet(2)).to.equal(other.address);
await idr.connect(other).unsetAgentWallet(2);
expect(await idr.getAgentWallet(2)).to.equal(ethers.ZeroAddress);
});
});
describe("Reputation Registry", function () {
beforeEach(async function () { await idr.connect(owner)["register(string)"]("ipfs://a1"); });
it("getIdentityRegistry; initialize runs once", async function () {
expect(await rep.getIdentityRegistry()).to.equal(await idr.getAddress());
await expect(rep.initialize(other.address)).to.be.revertedWithCustomError(rep, "AlreadyInitialized");
const fresh = await (await ethers.getContractFactory("AereReputationRegistry8004V2")).deploy(ethers.ZeroAddress);
await fresh.initialize(await idr.getAddress());
expect(await fresh.getIdentityRegistry()).to.equal(await idr.getAddress());
});
it("giveFeedback stores value/decimals/tags, emits NewFeedback with the exact field order, indexes from 1", async function () {
const h = ethers.keccak256(ethers.toUtf8Bytes("file"));
await expect(rep.connect(client).giveFeedback(1, 87, 0, "starred", "", "https://a/x", "ipfs://f", h))
.to.emit(rep, "NewFeedback").withArgs(1, client.address, 1, 87, 0, "starred", "starred", "", "https://a/x", "ipfs://f", h);
const r = await rep.readFeedback(1, client.address, 1);
expect(r.value).to.equal(87); expect(r.valueDecimals).to.equal(0); expect(r.tag1).to.equal("starred"); expect(r.isRevoked).to.equal(false);
expect(await rep.getLastIndex(1, client.address)).to.equal(1);
expect(await rep.getClients(1)).to.deep.equal([client.address]);
});
it("the submitter MUST NOT be the agent owner or an approved operator; valueDecimals MUST be 0..18; agent must exist", async function () {
await expect(rep.connect(owner).giveFeedback(1, 1, 0, "", "", "", "", ethers.ZeroHash)).to.be.revertedWithCustomError(rep, "SelfFeedback");
await idr.connect(owner).setApprovalForAll(other.address, true);
await expect(rep.connect(other).giveFeedback(1, 1, 0, "", "", "", "", ethers.ZeroHash)).to.be.revertedWithCustomError(rep, "SelfFeedback");
await expect(rep.connect(client).giveFeedback(1, 1, 19, "", "", "", "", ethers.ZeroHash)).to.be.revertedWithCustomError(rep, "BadDecimals");
await expect(rep.connect(client).giveFeedback(99, 1, 0, "", "", "", "", ethers.ZeroHash)).to.be.revertedWithCustomError(rep, "AgentNotRegistered");
});
it("revokeFeedback (own feedback only, once) and appendResponse (anyone) with their events and counts", async function () {
await rep.connect(client).giveFeedback(1, 5, 0, "", "", "", "", ethers.ZeroHash);
await expect(rep.connect(other).revokeFeedback(1, 1)).to.be.revertedWithCustomError(rep, "NoSuchFeedback");
await expect(rep.connect(client).revokeFeedback(1, 1)).to.emit(rep, "FeedbackRevoked").withArgs(1, client.address, 1);
await expect(rep.connect(client).revokeFeedback(1, 1)).to.be.revertedWithCustomError(rep, "AlreadyRevoked");
await expect(rep.connect(validator).appendResponse(1, client.address, 1, "ipfs://r", ethers.ZeroHash)).to.emit(rep, "ResponseAppended").withArgs(1, client.address, 1, validator.address, "ipfs://r", ethers.ZeroHash);
await rep.connect(other).appendResponse(1, client.address, 1, "", ethers.ZeroHash);
expect(await rep.getResponseCount(1, client.address, 1, [])).to.equal(2);
expect(await rep.getResponseCount(1, client.address, 1, [validator.address])).to.equal(1);
await expect(rep.connect(other).appendResponse(1, client.address, 7, "", ethers.ZeroHash)).to.be.revertedWithCustomError(rep, "NoSuchFeedback");
});
it("getSummary averages at the largest precision, filters by client list and tags, ignores revoked; readAllFeedback honours includeRevoked", async function () {
await rep.connect(client).giveFeedback(1, 80, 0, "starred", "", "", "", ethers.ZeroHash);
await rep.connect(client).giveFeedback(1, 9977, 2, "uptime", "", "", "", ethers.ZeroHash);
await rep.connect(other).giveFeedback(1, 60, 0, "starred", "", "", "", ethers.ZeroHash);
await rep.connect(other).giveFeedback(1, 1, 0, "starred", "", "", "", ethers.ZeroHash);
await rep.connect(other).revokeFeedback(1, 2);
let s = await rep.getSummary(1, [client.address, other.address], "starred", "");
expect(s.count).to.equal(2); expect(s.summaryValue).to.equal(70); expect(s.summaryValueDecimals).to.equal(0);
s = await rep.getSummary(1, [client.address], "", "");
expect(s.count).to.equal(2); expect(s.summaryValueDecimals).to.equal(2); expect(s.summaryValue).to.equal(8988); // (8000 + 9977) / 2, impartire intreaga
await expect(rep.getSummary(1, [], "", "")).to.be.revertedWithCustomError(rep, "EmptyClientList");
const all = await rep.readAllFeedback(1, [], "", "", false);
expect(all.clients.length).to.equal(3);
const withRevoked = await rep.readAllFeedback(1, [other.address], "starred", "", true);
expect(withRevoked.clients.length).to.equal(2); expect(withRevoked.revokedStatuses[1]).to.equal(true);
});
});
describe("Validation Registry", function () {
const H = ethers.keccak256(ethers.toUtf8Bytes("req"));
beforeEach(async function () { await idr.connect(owner)["register(string)"]("ipfs://a1"); });
it("validationRequest: owner or operator only; unique requestHash; event with indexed validator/agent/requestHash", async function () {
await expect(val.connect(other).validationRequest(validator.address, 1, "ipfs://q", H)).to.be.revertedWithCustomError(val, "NotAgentOwnerOrOperator");
await expect(val.connect(owner).validationRequest(validator.address, 1, "ipfs://q", H)).to.emit(val, "ValidationRequest").withArgs(validator.address, 1, "ipfs://q", H);
await expect(val.connect(owner).validationRequest(validator.address, 1, "ipfs://q", H)).to.be.revertedWithCustomError(val, "RequestExists");
expect(await val.requestCount()).to.equal(1);
expect(await val.getAgentValidations(1)).to.deep.equal([H]);
expect(await val.getValidatorRequests(validator.address)).to.deep.equal([H]);
await idr.connect(owner).approve(other.address, 1);
const H2 = ethers.keccak256(ethers.toUtf8Bytes("req2"));
await val.connect(other).validationRequest(validator.address, 1, "", H2);
});
it("validationResponse: only the named validator, response 0..100, repeatable with tags (soft then hard finality), status readable", async function () {
await val.connect(owner).validationRequest(validator.address, 1, "ipfs://q", H);
await expect(val.connect(other).validationResponse(H, 100, "", ethers.ZeroHash, "")).to.be.revertedWithCustomError(val, "NotTheValidator");
await expect(val.connect(validator).validationResponse(H, 101, "", ethers.ZeroHash, "")).to.be.revertedWithCustomError(val, "BadResponse");
await expect(val.connect(validator).validationResponse(H, 60, "ipfs://r1", ethers.ZeroHash, "soft")).to.emit(val, "ValidationResponse").withArgs(validator.address, 1, H, 60, "ipfs://r1", ethers.ZeroHash, "soft");
await val.connect(validator).validationResponse(H, 100, "ipfs://r2", ethers.ZeroHash, "hard");
const st = await val.getValidationStatus(H);
expect(st.validatorAddress).to.equal(validator.address); expect(st.agentId).to.equal(1); expect(st.response).to.equal(100); expect(st.tag).to.equal("hard");
await expect(val.getValidationStatus(ethers.ZeroHash)).to.be.revertedWithCustomError(val, "NoSuchRequest");
});
it("getSummary counts responded requests, filtered by validators and tag", async function () {
const H2 = ethers.keccak256(ethers.toUtf8Bytes("req2")), H3 = ethers.keccak256(ethers.toUtf8Bytes("req3"));
await val.connect(owner).validationRequest(validator.address, 1, "", H);
await val.connect(owner).validationRequest(validator.address, 1, "", H2);
await val.connect(owner).validationRequest(other.address, 1, "", H3);
await val.connect(validator).validationResponse(H, 100, "", ethers.ZeroHash, "hard");
await val.connect(validator).validationResponse(H2, 50, "", ethers.ZeroHash, "soft");
await val.connect(other).validationResponse(H3, 0, "", ethers.ZeroHash, "hard");
let s = await val.getSummary(1, [], "");
expect(s.count).to.equal(3); expect(s.averageResponse).to.equal(50);
s = await val.getSummary(1, [validator.address], "hard");
expect(s.count).to.equal(1); expect(s.averageResponse).to.equal(100);
});
});
});