// cancun-canary.test.js // // Proves the Cancun EVM feature set behind AereCancunCanary.sol, compiled // with solc 0.8.24 evmVersion=cancun via the per-file hardhat override // (the repo global stays 0.8.23). The local hardhat network runs with // hardfork: "cancun" (see hardhat.config.js), so these tests exercise the // real opcodes: // - TSTORE/TLOAD same-tx round-trip (EIP-1153) // - transient auto-reset between two SEPARATE transactions // - transient reentrancy lock (inner self-call must revert) // - MCOPY round-trip keccak equality (EIP-5656), multiple sizes // - BLOBHASH(0) == 0 and BLOBBASEFEE executes (EIP-4844 / EIP-7516) // - PUSH0 (EIP-3855) present at genuine opcode positions in the runtime // bytecode (opcode-walk, not a naive substring scan) const { expect } = require("chai"); const { ethers } = require("hardhat"); /** * Walk EVM bytecode from offset 0, skipping PUSH1..PUSH32 immediates, and * count 0x5f (PUSH0) at genuine opcode positions. Stops at the first * INVALID (0xfe) since solc places non-code data (metadata) after it. */ function countPush0(bytecodeHex) { const code = Buffer.from(bytecodeHex.replace(/^0x/, ""), "hex"); let i = 0; let push0 = 0; while (i < code.length) { const op = code[i]; if (op === 0xfe) break; // INVALID: data section follows if (op === 0x5f) push0++; if (op >= 0x60 && op <= 0x7f) { i += op - 0x60 + 1; // skip PUSHn immediate bytes } i += 1; } return push0; } describe("AereCancunCanary (solc 0.8.24, evmVersion=cancun)", () => { let canary, deployer; before(async () => { [deployer] = await ethers.getSigners(); const F = await ethers.getContractFactory("AereCancunCanary"); canary = await F.deploy(); await canary.waitForDeployment(); }); it("constructor self-test passed (TSTORE/TLOAD + MCOPY + blob opcodes)", async () => { expect(await canary.deploySelfTest()).to.equal(true); // constructor also emitted BlobProbed(0x0, fee) const rc = await canary.deploymentTransaction().wait(); const parsed = rc.logs .map((l) => { try { return canary.interface.parseLog(l); } catch { return null; } }) .filter(Boolean); const blob = parsed.find((e) => e.name === "BlobProbed"); expect(blob, "constructor BlobProbed event").to.not.equal(undefined); expect(blob.args.blobHash0).to.equal(ethers.ZeroHash); }); it("TSTORE/TLOAD: same-tx write + read-back round-trip", async () => { const value = 0xa3e3n; await expect(canary.transientWriteAndRead(value)) .to.emit(canary, "TransientWithinTx") .withArgs(value, value); }); it("transient storage auto-resets between two separate transactions", async () => { // tx 1: write a non-zero value into the transient slot const tx1 = await canary.transientWriteAndRead(999999n); const rc1 = await tx1.wait(); // tx 2 (a LATER, separate transaction): the slot must read 0 const tx2 = await canary.transientReadOnly(); const rc2 = await tx2.wait(); expect(rc2.blockNumber).to.be.greaterThanOrEqual(rc1.blockNumber); expect(tx2.hash).to.not.equal(tx1.hash); const parsed = rc2.logs .map((l) => { try { return canary.interface.parseLog(l); } catch { return null; } }) .filter(Boolean); const ev = parsed.find((e) => e.name === "TransientCrossTx"); expect(ev.args.observed).to.equal(0n); // and via eth_call as well expect(await canary.transientPeek()).to.equal(0n); }); it("transient reentrancy lock: inner self-call reverts, outer succeeds", async () => { await expect(canary.guardedEnter(true)) .to.emit(canary, "ReentrancyLockProbe") .withArgs(true, true); // reentry attempted, inner call reverted // lock is released after the tx (both by the modifier and by the // EVM transient reset) expect(await canary.lockPeek()).to.equal(0n); // a subsequent non-reentrant call goes straight through await expect(canary.guardedEnter(false)) .to.emit(canary, "ReentrancyLockProbe") .withArgs(false, false); }); it("MCOPY: round-trip keccak equality across sizes 0..1024", async () => { for (const n of [0, 1, 31, 32, 33, 256, 1024]) { const data = n === 0 ? "0x" : ethers.hexlify(ethers.randomBytes(n)); const [inputHash, copyHash] = await canary.mcopyHash(data); expect(copyHash, `mcopy size ${n}`).to.equal(inputHash); expect(inputHash).to.equal(ethers.keccak256(data)); } // transaction variant emits ok=true const data = ethers.hexlify(ethers.randomBytes(777)); await expect(canary.mcopyCheck(data)) .to.emit(canary, "McopyChecked") .withArgs(777n, ethers.keccak256(data), ethers.keccak256(data), true); }); it("BLOBHASH(0) == 0 in a non-blob tx; BLOBBASEFEE executes", async () => { const [h0, bbf] = await canary.blobView(); expect(h0).to.equal(ethers.ZeroHash); expect(bbf).to.be.a("bigint"); // recorded, not asserted (min is 1 wei) const tx = await canary.blobProbe(); const rc = await tx.wait(); const parsed = rc.logs .map((l) => { try { return canary.interface.parseLog(l); } catch { return null; } }) .filter(Boolean); const ev = parsed.find((e) => e.name === "BlobProbed"); expect(ev.args.blobHash0).to.equal(ethers.ZeroHash); }); it("PUSH0: deployed runtime bytecode contains genuine 0x5f opcodes", async () => { const code = await ethers.provider.getCode(await canary.getAddress()); expect(code.length).to.be.greaterThan(2); const n = countPush0(code); expect(n, "PUSH0 count at opcode positions").to.be.greaterThan(0); // canonical solc cancun/shanghai revert stub PUSH0 DUP1 REVERT expect(code.includes("5f80fd"), "5f80fd revert stub").to.equal(true); }); });