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.
136 lines
5.7 KiB
JavaScript
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);
|
|
});
|
|
});
|