Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
121 lines
5.2 KiB
JavaScript
121 lines
5.2 KiB
JavaScript
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");
|
|
});
|
|
});
|