aere-contracts/test/tokenbound.test.js
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
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.
2026-07-20 01:02:37 +03:00

136 lines
5.7 KiB
JavaScript

const { expect } = require("chai");
const { ethers } = require("hardhat");
// ERC-6551 token-bound accounts: canonical registry + AereTokenBoundAccount.
// Verifies that an NFT can own a wallet that (1) is deployed at the registry's
// deterministic address, (2) is controlled by the current NFT holder, (3) holds
// and moves native value via execute(), (4) produces EIP-1271 signatures for the
// holder, (5) tracks its ERC-6551 state, and (6) rejects non-holders and
// non-CALL operations.
describe("AereTokenBoundAccount (ERC-6551)", function () {
let registry, impl, nft, owner, other;
const SALT = ethers.ZeroHash;
const TOKEN_ID = 1n;
let chainId;
before(async function () {
[owner, other] = await ethers.getSigners();
chainId = (await ethers.provider.getNetwork()).chainId;
registry = await (await ethers.getContractFactory("ERC6551Registry")).deploy();
await registry.waitForDeployment();
impl = await (await ethers.getContractFactory("AereTokenBoundAccount")).deploy();
await impl.waitForDeployment();
nft = await (await ethers.getContractFactory("MockERC721")).deploy();
await nft.waitForDeployment();
await (await nft.mint(owner.address, TOKEN_ID)).wait();
});
it("account() is deterministic and createAccount() deploys there", async function () {
const predicted = await registry.account.staticCall(
await impl.getAddress(), SALT, chainId, await nft.getAddress(), TOKEN_ID
);
// no code yet
expect(await ethers.provider.getCode(predicted)).to.equal("0x");
const created = await registry.createAccount.staticCall(
await impl.getAddress(), SALT, chainId, await nft.getAddress(), TOKEN_ID
);
expect(created).to.equal(predicted);
await (await registry.createAccount(
await impl.getAddress(), SALT, chainId, await nft.getAddress(), TOKEN_ID
)).wait();
expect(await ethers.provider.getCode(predicted)).to.not.equal("0x");
// idempotent: same address on repeat
const again = await registry.account.staticCall(
await impl.getAddress(), SALT, chainId, await nft.getAddress(), TOKEN_ID
);
expect(again).to.equal(predicted);
});
async function boundAccount() {
const addr = await registry.account.staticCall(
await impl.getAddress(), SALT, chainId, await nft.getAddress(), TOKEN_ID
);
return ethers.getContractAt("AereTokenBoundAccount", addr);
}
it("token() returns the exact binding and owner() tracks the NFT holder", async function () {
const acct = await boundAccount();
const [cid, tc, tid] = await acct.token();
expect(cid).to.equal(chainId);
expect(tc).to.equal(await nft.getAddress());
expect(tid).to.equal(TOKEN_ID);
expect(await acct.owner()).to.equal(owner.address);
});
it("holds native value and executes a CALL as the holder; non-holder reverts", async function () {
const acct = await boundAccount();
const acctAddr = await acct.getAddress();
// fund the account
await (await owner.sendTransaction({ to: acctAddr, value: ethers.parseEther("1") })).wait();
expect(await ethers.provider.getBalance(acctAddr)).to.equal(ethers.parseEther("1"));
expect(await acct.state()).to.equal(0n);
// holder moves 0.4 AERE out to `other`
const before = await ethers.provider.getBalance(other.address);
await (await acct.connect(owner).execute(other.address, ethers.parseEther("0.4"), "0x", 0)).wait();
const after = await ethers.provider.getBalance(other.address);
expect(after - before).to.equal(ethers.parseEther("0.4"));
expect(await acct.state()).to.equal(1n); // state bumped
// a non-holder cannot execute
await expect(
acct.connect(other).execute(other.address, 0, "0x", 0)
).to.be.revertedWithCustomError(acct, "InvalidSigner");
// only CALL (operation 0) is supported
await expect(
acct.connect(owner).execute(other.address, 0, "0x", 1)
).to.be.revertedWithCustomError(acct, "UnsupportedOperation");
});
it("isValidSignature (EIP-1271) accepts the holder's signature and rejects others", async function () {
const acct = await boundAccount();
const digest = ethers.hexlify(ethers.randomBytes(32));
const goodSig = await owner.signMessage(ethers.getBytes(digest));
const ethDigest = ethers.hashMessage(ethers.getBytes(digest));
expect(await acct.isValidSignature(ethDigest, goodSig)).to.equal("0x1626ba7e");
const badSig = await other.signMessage(ethers.getBytes(digest));
expect(await acct.isValidSignature(ethDigest, badSig)).to.equal("0x00000000");
});
it("control follows the NFT: transferring it re-points owner()", async function () {
const acct = await boundAccount();
await (await nft.transferFrom(owner.address, other.address, TOKEN_ID)).wait();
expect(await acct.owner()).to.equal(other.address);
// now `other` controls it, `owner` does not
await (await acct.connect(other).execute(owner.address, 0, "0x", 0)).wait();
await expect(
acct.connect(owner).execute(owner.address, 0, "0x", 0)
).to.be.revertedWithCustomError(acct, "InvalidSigner");
// restore for isolation
await (await nft.connect(other).transferFrom(other.address, owner.address, TOKEN_ID)).wait();
});
it("supportsInterface reports ERC-165 / ERC-6551 / ERC-1271", async function () {
const acct = await boundAccount();
expect(await acct.supportsInterface("0x01ffc9a7")).to.equal(true); // ERC-165
expect(await acct.supportsInterface("0x6faff5f1")).to.equal(true); // IERC6551Account
expect(await acct.supportsInterface("0x51945447")).to.equal(true); // IERC6551Executable
expect(await acct.supportsInterface("0x1626ba7e")).to.equal(true); // IERC1271
expect(await acct.supportsInterface("0xffffffff")).to.equal(false);
});
});