aere-contracts/test/faucet-v2.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

100 lines
4.8 KiB
JavaScript

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("AereFaucetV2 — anonymous claim", () => {
let owner, signer, recipient, outsider;
let f;
beforeEach(async () => {
[owner, signer, recipient, outsider] = await ethers.getSigners();
const F = await ethers.getContractFactory("AereFaucetV2");
f = await F.deploy(signer.address);
await f.waitForDeployment();
// Seed 10 AERE.
await owner.sendTransaction({ to: await f.getAddress(), value: ethers.parseEther("10") });
});
async function ticket(commitment, recipientAddr, validUntil) {
const inner = ethers.AbiCoder.defaultAbiCoder().encode(
["string", "uint256", "address", "bytes32", "address", "uint64"],
["AereFaucetV2", (await ethers.provider.getNetwork()).chainId, await f.getAddress(), commitment, recipientAddr, validUntil]
);
const innerHash = ethers.keccak256(inner);
return signer.signMessage(ethers.getBytes(innerHash));
}
it("regular claim works with cooldown", async () => {
const before = await ethers.provider.getBalance(outsider.address);
await f.connect(outsider).claim();
const after = await ethers.provider.getBalance(outsider.address);
expect(after).to.be.gt(before); // roughly + dripAmount - gas
await expect(f.connect(outsider).claim()).to.be.revertedWithCustomError(f, "InCooldown");
});
it("anonymous claim with valid ticket sends drip to recipient", async () => {
const commit = ethers.keccak256(ethers.toUtf8Bytes("secret-1"));
const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
const sig = await ticket(commit, recipient.address, validUntil);
const before = await ethers.provider.getBalance(recipient.address);
await f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, sig);
const after = await ethers.provider.getBalance(recipient.address);
expect(after - before).to.equal(ethers.parseEther("0.05"));
expect(await f.spentCommitments(commit)).to.equal(true);
});
it("re-using a commitment reverts", async () => {
const commit = ethers.keccak256(ethers.toUtf8Bytes("secret-2"));
const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
const sig = await ticket(commit, recipient.address, validUntil);
await f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, sig);
await expect(
f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, sig)
).to.be.revertedWithCustomError(f, "CommitmentSpent");
});
it("expired ticket rejected", async () => {
const commit = ethers.keccak256(ethers.toUtf8Bytes("expired"));
const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 5);
const sig = await ticket(commit, recipient.address, validUntil);
await ethers.provider.send("evm_increaseTime", [10]);
await ethers.provider.send("evm_mine", []);
await expect(
f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, sig)
).to.be.revertedWithCustomError(f, "TicketExpired");
});
it("ticket signed by wrong key rejected", async () => {
const commit = ethers.keccak256(ethers.toUtf8Bytes("wrong-key"));
const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
// signed by `outsider` (not the SIGNER)
const inner = ethers.AbiCoder.defaultAbiCoder().encode(
["string", "uint256", "address", "bytes32", "address", "uint64"],
["AereFaucetV2", (await ethers.provider.getNetwork()).chainId, await f.getAddress(), commit, recipient.address, validUntil]
);
const innerHash = ethers.keccak256(inner);
const badSig = await outsider.signMessage(ethers.getBytes(innerHash));
await expect(
f.connect(outsider).claimAnonymous(commit, recipient.address, validUntil, badSig)
).to.be.revertedWithCustomError(f, "TicketSignerMismatch");
});
it("recipient bound in signed message — cannot redirect", async () => {
const commit = ethers.keccak256(ethers.toUtf8Bytes("redirect"));
const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
const sig = await ticket(commit, recipient.address, validUntil);
// Try claiming for a different recipient with the same ticket.
await expect(
f.connect(outsider).claimAnonymous(commit, outsider.address, validUntil, sig)
).to.be.revertedWithCustomError(f, "TicketSignerMismatch");
});
it("zero recipient + zero commitment rejected", async () => {
const validUntil = BigInt((await ethers.provider.getBlock("latest")).timestamp + 3600);
const sig = await ticket(ethers.ZeroHash, recipient.address, validUntil);
await expect(
f.connect(outsider).claimAnonymous(ethers.ZeroHash, recipient.address, validUntil, sig)
).to.be.revertedWithCustomError(f, "BadParams");
});
});