const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("AereAgentMemoryVault", () => { let user, agent1, agent2, outsider; let v; const SLOT_PREFS = ethers.keccak256(ethers.toUtf8Bytes("user.preferences")); const SLOT_TONE = ethers.keccak256(ethers.toUtf8Bytes("user.tone")); const HASH = ethers.keccak256(ethers.toUtf8Bytes("encrypted-blob")); beforeEach(async () => { [user, agent1, agent2, outsider] = await ethers.getSigners(); const F = await ethers.getContractFactory("AereAgentMemoryVault"); v = await F.deploy(); await v.waitForDeployment(); }); async function nowTs() { return (await ethers.provider.getBlock("latest")).timestamp; } it("user grants permission + agent writes + head updates", async () => { await v.connect(user).grant(agent1.address, SLOT_PREFS, true, true, 0); await expect( v.connect(agent1).write(user.address, SLOT_PREFS, HASH, "ipfs://blob") ).to.emit(v, "EntryWritten").withArgs(user.address, SLOT_PREFS, agent1.address, 1n, HASH, "ipfs://blob"); const head = await v.headOf(user.address, SLOT_PREFS); expect(head.contentHash).to.equal(HASH); expect(head.writer).to.equal(agent1.address); expect(head.version).to.equal(1n); expect(await v.nextVersion(user.address, SLOT_PREFS)).to.equal(1n); }); it("write reverts without canWrite or with empty hash", async () => { await expect( v.connect(agent1).write(user.address, SLOT_PREFS, HASH, "") ).to.be.revertedWithCustomError(v, "NotWriter"); await v.connect(user).grant(agent1.address, SLOT_PREFS, true, false, 0); // read-only await expect( v.connect(agent1).write(user.address, SLOT_PREFS, HASH, "") ).to.be.revertedWithCustomError(v, "NotWriter"); await v.connect(user).grant(agent1.address, SLOT_PREFS, true, true, 0); await expect( v.connect(agent1).write(user.address, SLOT_PREFS, ethers.ZeroHash, "") ).to.be.revertedWithCustomError(v, "EmptyHash"); }); it("permission expiry blocks new writes", async () => { const t = await nowTs(); await v.connect(user).grant(agent1.address, SLOT_PREFS, true, true, t + 5); await v.connect(agent1).write(user.address, SLOT_PREFS, HASH, ""); await ethers.provider.send("evm_increaseTime", [10]); await ethers.provider.send("evm_mine", []); await expect( v.connect(agent1).write(user.address, SLOT_PREFS, HASH, "") ).to.be.revertedWithCustomError(v, "PermissionExpired"); }); it("grant with already-elapsed validUntil reverts", async () => { const t = await nowTs(); await expect( v.connect(user).grant(agent1.address, SLOT_PREFS, true, true, t - 1) ).to.be.revertedWithCustomError(v, "InvalidValidity"); }); it("user revoke blocks future writes + flips canReadNow", async () => { await v.connect(user).grant(agent1.address, SLOT_PREFS, true, true, 0); expect(await v.canReadNow(user.address, agent1.address, SLOT_PREFS)).to.equal(true); await v.connect(user).revoke(agent1.address, SLOT_PREFS); expect(await v.canReadNow(user.address, agent1.address, SLOT_PREFS)).to.equal(false); await expect( v.connect(agent1).write(user.address, SLOT_PREFS, HASH, "") ).to.be.revertedWithCustomError(v, "NotWriter"); }); it("revoke without prior grant reverts", async () => { await expect( v.connect(user).revoke(agent1.address, SLOT_PREFS) ).to.be.revertedWithCustomError(v, "NoSuchPermission"); }); it("portability — second agent gets the same head after fresh grant", async () => { await v.connect(user).grant(agent1.address, SLOT_PREFS, true, true, 0); await v.connect(agent1).write(user.address, SLOT_PREFS, HASH, "ipfs://v1"); await v.connect(agent1).write(user.address, SLOT_PREFS, HASH, "ipfs://v2"); const headV1 = await v.headOf(user.address, SLOT_PREFS); expect(headV1.version).to.equal(2n); // User onboards agent2 with read-only access. await v.connect(user).grant(agent2.address, SLOT_PREFS, true, false, 0); expect(await v.canReadNow(user.address, agent2.address, SLOT_PREFS)).to.equal(true); const headRead = await v.headOf(user.address, SLOT_PREFS); expect(headRead.version).to.equal(2n); // But agent2 cannot write yet. await expect( v.connect(agent2).write(user.address, SLOT_PREFS, HASH, "") ).to.be.revertedWithCustomError(v, "NotWriter"); }); it("each user namespace is isolated", async () => { await v.connect(user).grant(agent1.address, SLOT_PREFS, true, true, 0); await v.connect(agent1).write(user.address, SLOT_PREFS, HASH, ""); // outsider's namespace untouched. const otherHead = await v.headOf(outsider.address, SLOT_PREFS); expect(otherHead.version).to.equal(0n); // Agent has no permission in outsider's namespace. await expect( v.connect(agent1).write(outsider.address, SLOT_PREFS, HASH, "") ).to.be.revertedWithCustomError(v, "NotWriter"); }); it("per-slot grain: granting one slot does not grant another", async () => { await v.connect(user).grant(agent1.address, SLOT_PREFS, true, true, 0); await expect( v.connect(agent1).write(user.address, SLOT_TONE, HASH, "") ).to.be.revertedWithCustomError(v, "NotWriter"); }); });